2010-09-04 47 views
3

在我追求更多F#的過程中,我試圖實現一個由Paul Graham here描述的「蓄能器發電機」。我最好的解決方案至今是完全動態類型:F中的蓄能器發電機#

open System 

let acc (init:obj) : obj->obj= 
    let state = ref init 
    fun (x:obj) -> 
    if (!state).GetType() = typeof<Int32> 
     && x.GetType() = typeof<Int32> then 
     state := (Convert.ToInt32(!state) + Convert.ToInt32(x)) :> obj 
    else 
     state := (Convert.ToDouble(!state) + Convert.ToDouble(x)) :> obj 
    !state 

do 
    let x : obj -> obj = acc 1 // the type annotation is necessary here 
    (x 5) |> ignore 
    printfn "%A" (x 2) // prints "8" 
    printfn "%A" (x 2.3) // prints "10.3" 

我有三個問題:

  • 如果我刪除了x類型標註的代碼無法編譯,因爲編譯器推斷X形int -> obj - 雖然acc已註釋返回obj->obj。爲什麼是這樣,我可以避免它?
  • 任何想法來改善這種動態類型的版本?
  • 是否有可能用適當的靜態類型來實現它?也許會員約束? (這可能在Haskell中,但不在OCaml,AFAIK中)

回答

8

在我追求更多F#的過程中,我試着實現一個由Paul Graham在這裏描述的「累加器生成器」。

此問題需要存在未指定的數字塔。 Lisp碰巧有一個,它恰好適用於Paul Graham的例子,因爲這個問題是專門爲了讓Lisp看起來很人性化而設計的。

您可以使用聯合類型(如type number = Int of int | Float of float)或通過裝箱所有內容來實現F#中的數字塔。以下解決方案使用後一種方法:

let add (x: obj) (y: obj) = 
    match x, y with 
    | (:? int as m), (:? int as n) -> box(m+n) 
    | (:? int as n), (:? float as x) 
    | (:? float as x), (:? int as n) -> box(x + float n) 
    | (:? float as x), (:? float as y) -> box(x + y) 
    | _ -> failwith "Run-time type error" 

let acc x = 
    let x = ref x 
    fun (y: obj) -> 
    x := add !x y 
    !x 

let x : obj -> _ = acc(box 1) 
do x(box 5) 
do acc(box 3) 
do printfn "%A" (x(box 2.3)) 

但是,數字塔在現實世界中實際上是無用的。除非你非常小心,試圖從這些種類的挑戰中學習會對你造成更多的傷害而不是好處。你應該問自己爲什麼我們不想要一個數字塔,不想要框和不想運行時類型的提升?

爲什麼我們不只是寫:

let x = 1 
let x = x + 5 
ignore(3) 
let x = float x + 2.3 

我們知道的x在每一步的類型。每個數字都將被拆箱。我們知道這段代碼永遠不會產生運行時類型錯誤...

3

絕對不可能用適當的靜態類型實現它。你說你可以在Haskell,但我不相信你。

+0

你說得對。我對Haskell不太瞭解。我在接受的解決方案頁面上看到了Haskell(http://www.paulgraham.com/accgen.html),並認爲它可行。 現在我嘗試了它,我發現Haskell解決方案似乎並不能滿足要求(它靜態地決定如果您在任何地方使用帶浮點的生成函數,則一直使用浮點數)。 – wmeyer 2010-09-05 01:17:01

6

我同意Jon的觀點,這是一個很人造的例子,它不是學習F#的好起點。但是,您可以使用靜態成員約束來在沒有動態轉換和反射的情況下合理地關閉它。如果你把它標記爲inline並添加使用float都轉換參數:

let inline acc x = 
    let x = ref (float x) 
    fun y -> 
    x := (float y) + !x 
    !x 

你會得到一個功能與以下類型:

val inline acc : 
    ^a -> (^b -> float) 
    when ^a : (static member op_Explicit : ^a -> float) and 
      ^b : (static member op_Explicit : ^b -> float) 

該函數接受任何兩個參數,可以顯式轉換爲float。與LISP版本(我猜)相比,唯一的限制是它總是返回float(作爲可用的最通用數字類型)。你可以寫類似:

> acc 1 2;;   // For two integers, it returns float 
val it : float = 3.0 
> acc 1 2.1;;   // integer + float 
val it : float = 3.1 
> acc 1 "31";;   // It even works with strings! 
val it : float = 32.0 
+0

問題陳述需要'int - > int - > int',但是你的代碼將會返回'float'。 – 2010-09-05 13:30:31

+0

@Jon:是的,這就是爲什麼我寫道「沒有使用動態演員時相當接近」。在實踐中,打破其中一個要求(其中的一小部分)可能比使用裝箱和拆箱更好。 – 2010-09-05 14:09:45

0

的問題,試圖用靜態類型要做到這一點是可能加入不同類型的兩個不同的號碼,同時保留了左側的類型。正如喬恩哈羅普所說,這是可能的聯盟類型。一旦你定義了聯合類型和相應的加法運算,如前所述,實際的累加器非常簡單。我的執行:

module MyTest 

type Numeric = 
    | NInt of int 
    | NFloat of float 

    member this.Add(other : Numeric) : Numeric = 
    match this with 
     | NInt x -> 
     match other with 
      | NInt y -> NInt (x + y) 
      | NFloat y -> NInt (x + (int y)) 
     | NFloat x -> 
     match other with 
      | NInt y -> NFloat (x + (float y)) 
      | NFloat y -> NFloat (x + y) 

    override this.ToString() = 
    match this with 
     | NInt x -> x.ToString() 
     | NFloat x -> x.ToString() 

let foo (n : Numeric) = 
    let acc = ref n 
    fun i -> 
    acc := (!acc).Add(i) 
    !acc 

let f = foo (NFloat 1.1) 
(2 |> NInt |> f).ToString() |> printfn "%s"