Frage

In meiner Suche mehr F # zu lernen, habe ich versucht, einen „Akkumulator-Generator“ von Paul Graham hier beschrieben zu implementieren . Meine beste Lösung bisher vollständig dynamisch typisiert:

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"

Ich habe drei Fragen:

  • Wenn ich die Typanmerkung für x entfernen, wird der Code schlägt fehl, da der Compiler folgert Typ int -> obj für x zu kompilieren - obwohl acc kommentierten ist ein obj->obj zurückzukehren. Warum ist das so und wie kann ich es vermeiden?
  • Alle Ideen, diese dynamisch typisierte Version zu verbessern?
  • Ist es möglich, dies mit dem richtigen statischen Typen zu implementieren? Vielleicht mit Mitglied Einschränkungen? (Es ist möglich, in Haskell, aber nicht in OCaml, AFAIK)
War es hilfreich?

Lösung

  

In meiner Suche mehr F # zu lernen, habe ich versucht, einen „Akkumulator-Generator“, wie von Paul Graham hier beschrieben zu implementieren.

Dieses Problem erfordert die Existenz eines nicht spezifizierten numerischen Turm. Lisp geschieht, einen haben, und es geschieht für Paul Grahams Beispiele ausreichend zu sein, weil dieses Problem speziell zu machen Lisp Aussehen künstlich gut entworfen wurde.

Sie können einen numerischen Turm in F # implementieren entweder eine Vereinigung Typ (wie type number = Int of int | Float of float) oder durch Boxen alles. Die folgende Lösung verwendet den letzteren Ansatz:

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

Allerdings Zahlentürme sind praktisch nutzlos in der realen Welt. Es sei denn, Sie sind sehr vorsichtig und versuchen, von dieser Art von borked Herausforderungen zu lernen, werden Sie mehr schaden als nützen. Sie verlassen sollte man sich fragen, warum wir nicht einen numerischen Turm wollen wollen nicht Box und wollen nicht, Typ Förderung Laufzeit?

Warum wir nicht einfach schreiben:

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

Wir wissen, welche Art von x bei jedem Schritt. Jede Nummer wird gespeichert unboxed. Wir wissen, dass dieser Code nie einen Laufzeittypfehler erzeugen ...

Andere Tipps

Ich bin damit einverstanden Jon mit, dass dies ziemlich künstlich Beispiel und es ist nicht ein guter Ausgangspunkt für F # lernen. Sie können jedoch statisches Element Constraints maßen nahe ohne dynamische Casts und Reflexion zu erhalten. Wenn Sie es als inline markieren und fügen Sie beide Parameter konvertieren float mit:

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

Sie erhalten eine Funktion mit der folgenden Art erhalten:

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

Die Funktion nimmt alle zwei Argumente, die explizit zu schweben umgewandelt werden können. Die einzige Einschränkung für die Lisp-Version verglichen (I guess) ist, dass es Schwimmer immer wieder (als universellste numerischen Typ verfügbar). Sie können so etwas wie schreiben:

> 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

Es ist definitiv nicht möglich, dies mit dem richtigen statischen Typen zu implementieren. Sie sagen, Sie in Haskell kann, aber ich glaube Ihnen nicht.

Das Problem mit dem Versuch, diese mit statischer Typisierung zu tun ist, in das Hinzufügen von zwei unterschiedlichen Anzahl von möglicherweise unterschiedlichen Typen, während die Art der auf der linken Seite zu erhalten. Als Jon Harrop sagt dies mit einem Union-Typ möglich ist. Sobald Sie den Union-Typen und eine entsprechende Additionsoperation definiert haben, die wie erwähnt arbeitet, ist der tatsächliche Akku sehr einfach. Meine Implementierung:

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"
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top