Question

Dans ma quête pour en savoir plus F #, j'ai essayé de mettre en œuvre un « générateur d'accumulateur » comme décrit par Paul Graham ici . Ma meilleure solution jusqu'à présent est complètement typé dynamiquement:

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"

J'ai trois questions:

  • Si je supprime l'annotation de type pour x, le code ne peut pas compiler car le compilateur infère de type int -> obj pour x - bien que acc est annotées pour retourner un obj->obj. Pourquoi est-ce que je peux et éviter?
  • Toutes les idées pour améliorer cette version typée dynamiquement?
  • Est-il possible de mettre en œuvre ce type avec des statiques appropriés? Peut-être avec des contraintes membres? (Il est possible dans Haskell, mais pas dans OCaml, AFAIK)
Était-ce utile?

La solution

  

Dans ma quête pour en savoir plus F #, j'ai essayé de mettre en œuvre un « générateur d'accumulateurs » tel que décrit par Paul Graham ici.

Ce problème nécessite l'existence d'une tour numérique non spécifiée. Lisp arrive à avoir une et il arrive d'être suffisant pour les exemples de Paul Graham parce que ce problème a été spécialement conçu pour faire paraître artificiellement Lisp bien.

Vous pouvez mettre en œuvre une tour numérique en F #, soit en utilisant un type d'union (comme type number = Int of int | Float of float) ou par tout boxe. La solution suivante utilise cette dernière approche:

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

Cependant, les tours numériques sont pratiquement inutiles dans le monde réel. Sauf si vous êtes très prudent, en essayant d'apprendre de ce genre de défis foireuse ne vous fera plus de mal que de bien. Vous devriez laisser vous demander pourquoi nous ne voulons pas une tour numérique, ne voulez pas à la boîte et faire de la promotion de type de temps d'exécution veulent pas?

Pourquoi ne pas écrire que:

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

Nous savons le type de x à chaque étape. Chaque numéro est enregistré Unboxed. Nous savons que ce code ne produira jamais une erreur de type run-time ...

Autres conseils

Je suis d'accord avec Jon que cela est tout à fait par exemple artificiel et il est un bon point de départ pour l'apprentissage F #. Cependant, vous pouvez utiliser des contraintes membres statiques pour obtenir raisonnablement fermer sans moulages dynamiques et de réflexion. Si vous marquez comme inline et ajoutez convertir à la fois des paramètres à l'aide float:

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

Vous aurez une fonction du type suivant:

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

La fonction prend les deux arguments qui peuvent être convertis explicitement flotter. La seule limite par rapport à la version LISP (je suppose) est qu'il retourne toujours flottant (comme le type le plus universel numérique disponible). Vous pouvez écrire quelque chose comme:

> 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

Il est certainement pas possible de mettre en œuvre ce avec des types de statiques appropriés. Vous dites que vous pouvez en Haskell, mais je ne crois pas.

Le problème à essayer de le faire avec le typage statique est en ajoutant deux nombres différents de types différents peut-être tout en préservant le type de côté gauche. Comme Jon Harrop dit cela est possible avec un type d'union. Une fois que vous avez défini le type d'union et une opération d'addition correspondant qui fonctionne comme mentionné précédemment, l'accumulateur réel est très simple. Ma mise en œuvre:

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"
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top