题
为了了解更多f#,我试图实现保罗·格雷厄姆(Paul Graham)所描述的“蓄能器发生器” 这里. 。到目前为止,我最好的解决方案是完全动态键入的:
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
, ,代码无法编译,因为编译器输入类型int -> obj
x-虽然acc
注释以返回obj->obj
. 。为什么这样做,我可以避免它? - 有什么想法可以改善该动态键入的版本吗?
- 是否可以使用适当的静态类型实现此目标?也许有会员约束吗? (在Haskell中可能有可能,但在Ocaml,Afaik中不可能)
解决方案
为了了解更多F#,我尝试实现Paul Graham在这里所描述的“蓄能器发生器”。
这个问题需要存在未指定的数字塔。 LISP恰好有一个,恰好适合保罗·格雷厄姆(Paul Graham)的例子,因为这个问题是专门设计的,以使LISP看起来人为地看起来不错。
您可以使用联合类型在f#中实现数字塔(例如 type number = Int of int | Float of float
)或拳击一切。以下解决方案采用后一种方法:
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
步步。每个数字均存储了未盒装。我们知道此代码永远不会产生运行时类型错误...
其他提示
我同意乔恩(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)
该函数采用任何两个可以明确转换为浮点的参数。与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
绝对不可能使用适当的静态类型实施此功能。您说您可以在Haskell中,但我不相信您。
尝试使用静态键入执行此操作的问题在于在保留左侧的类型时添加两种不同数量的可能不同类型。正如乔恩·哈罗普(Jon Harrop)所说的那样,这是有工会类型的。一旦定义了联合类型和相应的添加操作,如上所述,实际的累加器非常简单。我的实施:
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"