Accumulator Generator in F #
-
30-09-2019 - |
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 Typint -> obj
für x zu kompilieren - obwohlacc
kommentierten ist einobj->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)
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"