Вопрос

В моем стремлении узнать больше 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, код не может компилировать, потому что тип компилятора типа int -> obj для х - хотя acc аннотируется, чтобы вернуть obj->obj. Отказ Почему это и я могу избежать этого?
  • Любые идеи, чтобы улучшить эту динамически набранную версию?
  • Можно ли реализовать это с правильными статическими типами? Может быть, с ограничениями члена? (Это возможно в Haskell, но не в Ocaml, Afaik)
Это было полезно?

Решение

В моем стремлении узнать больше F #, я пытался реализовать «генератор аккумулятора», как описано здесь Полом Грэм.

Эта проблема требует существования неопределенной числовой башни. У Лисс есть один, и это происходит адекватным для примеров Павла Грэма, потому что эта проблема была специально разработана, чтобы изготовить Лискус выглядеть искусственно хорошим.

Вы можете реализовать цифровую башню в F # либо с использованием типа Union (например, 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 на каждом шагу. Каждое количество хранится Unboxed. Мы знаем, что этот код никогда не даст ошибку типа выполнения ...

Другие советы

Я согласен с Джоном, что это довольно искусственный пример, и это не хорошая отправная точка для обучения 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

Это определенно невозможно реализовать это с соответствующими статическими типами. Вы говорите, что можете в Haskell, но я тебе не верю.

Проблема с попыткой сделать это со статическими наборами, находится в добавлении двух разных чисел, возможно, различных типов, сохраняя тип левой стороны. Как говорит Джон Харруп, это возможно с типом профсоюза. Как только вы определили тип Union и соответствующую операцию добавления, которая работает, как упомянуто, фактический аккумулятор очень прост. Моя реализация:

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"
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top