2014-02-24 39 views
2

我试图在一个list利用的flexible types异构列表

type IFilter<'a> = 
    abstract member Filter: 'a -> 'a 

type Cap<'a when 'a: comparison> (cap) = 
    interface IFilter<'a> with 
     member this.Filter x = 
      if x < cap 
      then x 
      else cap 

type Floor<'a when 'a: comparison> (floor) = 
    interface IFilter<'a> with 
     member this.Filter x = 
      if x > floor 
      then x 
      else floor 

type Calculator<'a, 'b when 'b:> IFilter<'a>> (aFilter: 'b, operation: 'a -> 'a) = 
    member this.Calculate x = 
     let y = x |> operation 
     aFilter.Filter y 

type TowerControl<'a>() = 
    let mutable calculationStack = List.empty 
    member this.addCalculation (x: Calculator<'a, #IFilter<'a>>) = 
     let newList = x::calculationStack 
     calculationStack <- newList 

let floor10 = Floor<int> 10 
let calc1 = Calculator<int, Floor<int>> (floor10, ((+) 10)) 

let cap10 = Cap 10 
let calc2 = Calculator (cap10, ((-) 5)) 

let tower = TowerControl<int>() 
tower.addCalculation calc1 
tower.addCalculation calc2 

在该示例粘以上

member this.addCalculation (x: Calculator<'a, #IFiler<'a>>) = 

异构类型产生错误

错误FS0670:此代码不够通用。类型变量'a不能一概而论,因为它会逃避它的范围。

如果已经发布了类似的问题,我们深表歉意。 谢谢。

+0

我怀疑添加''a'注释到计算堆可能会有所帮助。 –

+0

谢谢。我尝试了'让可变计算堆栈<'a> = List.empty'这导致'错误FS0830:可变值不能有通用参数' – NoIdeaHowToFixThis

+0

正确的形式是'让可变计算堆栈:'列表= ...' –

回答

3

有没有简单的方法来做到这一点。它看起来像你真的想calculationStack有类型:

(∃('t:>IFilter<'a>).Calculator<'a, 't>) list 

但F#不提供存在的类型。您可以使用“双否定编码” ∃'t.f<'t> = ∀'x.(∀'t.f<'t>->'x)->'x拿出了以下解决方法:

// helper type representing ∀'t.Calculator<'t>->'x 
type AnyCalc<'x,'a> = abstract Apply<'t when 't :> IFilter<'a>> : Calculator<'a,'t> -> 'x 

// type representing ∃('t:>IFilter<'a>).Calculator<'a, 't> 
type ExCalc<'a> = abstract Apply : AnyCalc<'x,'a> -> 'x 

// packs a particular Calculator<'a,'t> into an ExCalc<'a> 
let pack f = { new ExCalc<'a> with member this.Apply(i) = i.Apply f } 

// all packing and unpacking hidden here 
type TowerControl<'a>() = 
    let mutable calculationStack = List.empty 

    // note: type inferred correctly! 
    member this.addCalculation x = 
     let newList = (pack x)::calculationStack 
     calculationStack <- newList 

    // added this to show how to unpack the calculations for application 
    member this.SequenceCalculations (v:'a) = 
     calculationStack |> List.fold (fun v i -> i.Apply { new AnyCalc<_,_> with member this.Apply c = c.Calculate v }) v 

// the remaining code is untouched 

let floor10 = Floor<int> 10 
let calc1 = Calculator<int, Floor<int>> (floor10, ((+) 10)) 

let cap10 = Cap 10 
let calc2 = Calculator (cap10, ((-) 5)) 

let tower = TowerControl<int>() 
tower.addCalculation calc1 
tower.addCalculation calc2 

这具有很大的优势,它不需要修改Calculator<_,_>类型,并且语义正是你想要的,但有以下缺点:

  1. 如果你不熟悉这种编码存在的方式很难遵循。
  2. 即使你很熟悉,也有很多丑陋的样板(两种帮手类型),因为F#不允许匿名通用资格。也就是说,即使考虑到F#不直接支持存在类型,它会更容易阅读,如果你能写的东西,如:

    type ExCalc<'a> = ∀'x.(∀('t:>IFilter<'a>).Calculator<'a,'t>->'x)->'x 
    let pack (c:Calculator<'a,'t>) : ExCalc<'a> = fun f -> f c 
    
    type TowerControl<'a>() = 
        ... 
        member this.SequenceCalcualtions (v:'a) = 
         calculationStack |> List.fold (fun v i -> i (fun c -> c.Calculate v)) v 
    

    但是相反,我们得拿出两个助手名称类型和他们的单一方法。这最终导致代码难以遵循,即使对于已经熟悉这种通用技术的人也是如此。

在所拥有的Calculator<_,_>类起飞的机会,还有一个更简单的解决方案,可能的工作(这也取决于真正Calcuator的方法< >类,如果是签名比您在此处介绍的要复杂得多):引入一个ICalculator<'a>接口,使Calculator<_,_>实现该接口,并使calculationStack为该接口类型的值列表。这会让人们更容易理解,但只有当你拥有Calculator<_,_>(或者如果已经有一个现有的可以继续使用的界面)时才可能。你甚至可以将接口设置为私有的,这样只有你的代码知道它的存在。这是看起来如何:

type private ICalculator<'a> = abstract Calculate : 'a -> 'a 

type Calculator<'a, 'b when 'b:> IFilter<'a>> (aFilter: 'b, operation: 'a -> 'a) = 
    member this.Calculate x = 
     let y = x |> operation 
     aFilter.Filter y 
    interface ICalculator<'a> with 
     member this.Calculate x = this.Calculate x 

type TowerControl<'a>() = 
    let mutable calculationStack = List.empty 
    member this.addCalculation (x: Calculator<'a, #IFilter<'a>>) = 
     let newList = (x :> ICalculator<'a>)::calculationStack 
     calculationStack <- newList 

    member this.SequenceCalculations (v:'a) = 
     calculationStack |> List.fold (fun v c -> c.Calculate v) v 
+0

谢谢你的帮助。我不熟悉存在和普遍类型。我会深入挖掘,我想了解你的代码。 – NoIdeaHowToFixThis

+2

@NoIdeaHowToFixThis - 我在答案的末尾添加了一个更简单的替代方法,并附上一些关于何时适用的评论。 – kvb

+0

假设'Calculator <_,_>'类可以被修改,那么为了提供基类'CalculatorBase <'a>(aFilter:IFilter <'a>,operation:'a - >'a)''''' ,可以导出期望的类'类型计算器''a,'b何时'b:> IFilter <'a>>(aFilter:'b,operation)'? – kaefer