F# 中的累加器生成器

发布于 2024-09-18 10:40:44 字数 986 浏览 10 评论 0原文

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

In my quest to learn more F#, I tried to implement an "accumulator generator" as described by Paul Graham here. My best solution so far is completely dynamically typed:

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"

I have three questions:

  • If I remove the type annotation for x, the code fails to compile because the compiler infers type int -> obj for x - although acc is annotated to return an obj->obj. Why is that and can I avoid it?
  • Any ideas to improve this dynamically typed version?
  • Is it possible to implement this with proper static types? Maybe with member constraints? (It is possible in Haskell, but not in OCaml, AFAIK)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

你不是我要的菜∠ 2024-09-25 10:40:44

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

In my quest to learn more F#, I tried to implement an "accumulator generator" as described by Paul Graham here.

This problem requires the existence of an unspecified numeric tower. Lisp happens to have one and it happens to be adequate for Paul Graham's examples because this problem was specifically designed to make Lisp look artificially good.

You can implement a numeric tower in F# either using a union type (like type number = Int of int | Float of float) or by boxing everything. The following solution uses the latter approach:

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))

However, numeric towers are virtually useless in the real world. Unless you are very careful, trying to learn from these kinds of borked challenges will do you more harm than good. You should leave asking yourself why we do not want a numeric tower, do not want to box and do not want run-time type promotion?

Why didn't we just write:

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

We know the type of x at every step. Every number is stored unboxed. We know that this code will never produce a run-time type error...

挽梦忆笙歌 2024-09-25 10:40:44

我同意 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

I agree with Jon that this is quite artificial example and it is not a good starting point for learning F#. However, you can use static member constraints to get reasonably close without dynamic casts and reflection. If you mark it as inline and add convert both of the parameters using float:

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

You'll get a function with the following type:

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

The function takes any two arguments that can be explicitly converted to float. The only limitation compared to the LISP version (I guess) is that it always returns float (as the most universal numeric type available). You can write something like:

> 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
烟雨扶苏 2024-09-25 10:40:44

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

It's definitely not possible to implement this with proper static types. You say you can in Haskell, but I don't believe you.

迷荒 2024-09-25 10:40:44

尝试使用静态类型来做到这一点的问题在于添加两个可能不同类型的不同数量,同时保留左侧的类型。正如 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"

The problem with trying to do this with static typing is in adding two different numbers of possibly different types while preserving the type of the left-hand side. As Jon Harrop says this is possible with a union type. Once you've defined the union type and a corresponding addition operation which works as mentioned, the actual accumulator is very simple. My implementation:

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"
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文