通过灵活类型的异构列表

时间:2014-02-24 15:53:11

标签: list f# subtyping

我正试图在使用flexible types

list中粘贴异构类型
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无法推广,因为它会逃避其范围。

如果已发布类似问题,请致歉。 谢谢。

1 个答案:

答案 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
    

    但我们必须为辅助类型及其单一方法提供名称。这最终使代码很难遵循,即使对于已经熟悉一般技术的人也是如此。

  3. 如果您拥有Calculator<_,_>类,那么可以使用更简单的解决方案(它可能还取决于真实计算器的方法的签名&lt; &gt;类,如果它比你在这里提到的更复杂):引入一个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