2

为了了解更多 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 -> objx 的类型 - 尽管acc被注释为返回一个obj->obj. 为什么会这样,我可以避免吗?
  • 有什么想法可以改进这个动态类型的版本吗?
  • 是否可以使用适当的静态类型来实现这一点?也许有成员限制?(在 Haskell 中可以,但在 OCaml、AFAIK 中不行)
4

4 回答 4

8

为了了解更多 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每一步的类型。每个号码都未装箱存储。我们知道这段代码永远不会产生运行时类型错误......

于 2010-09-05T11:21:37.597 回答
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)

该函数接受任何两个可以显式转换为浮点数的参数。与 LISP 版本(我猜)相比,唯一的限制是它总是返回浮点数(作为最通用的数字类型可用)。您可以编写如下内容:

> 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
于 2010-09-05T11:45:01.407 回答
3

使用适当的静态类型绝对不可能实现这一点。你说你可以在 Haskell 中,但我不相信你。

于 2010-09-05T00:39:08.900 回答
0

尝试使用静态类型执行此操作的问题在于添加两个不同数量的可能不同类型,同时保留左侧的类型。正如 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"
于 2014-06-09T04:48:40.580 回答