Аккумуляторный генератор в F # - PullRequest
3 голосов
/ 05 сентября 2010

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

Ответы [ 4 ]

8 голосов
/ 05 сентября 2010

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

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

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

6 голосов
/ 05 сентября 2010

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

Определенно невозможно реализовать это с правильными статическими типами. Ты говоришь, что можешь в Хаскеле, но я тебе не верю.

0 голосов
/ 09 июня 2014

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

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"
...