2014-02-24 44 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