Pregunta

En mi búsqueda para aprender más F #, he tratado de poner en práctica un "generador de acumulador" como se describe por Paul Graham aquí . Mi mejor solución hasta ahora es completamente tipos dinámicos:

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"

Tengo tres preguntas:

  • Si quito el tipo de anotación para x, el código falla al compilar porque el tipo infiere compilador int -> obj para x - a pesar de acc se anota para devolver un obj->obj. ¿Por qué es eso y puedo evitarlo?
  • Todas las ideas para mejorar esta versión de tipos dinámicos?
  • ¿Es posible aplicar esto con tipos estáticos adecuados? Tal vez con limitaciones miembros? (Es posible en Haskell, pero no en OCaml, AFAIK)
¿Fue útil?

Solución

  

En mi búsqueda para aprender más F #, he tratado de poner en práctica un "generador de acumulador" como se describe por Paul Graham aquí.

Este problema requiere la existencia de una torre numérica especificada. Lisp pasa a tener uno y sucede que es adecuada para ejemplos de Paul Graham porque este problema fue diseñado específicamente para hacer Lisp mirada artificialmente buena.

Se puede aplicar una torre numérica en C #, ya sea utilizando un tipo de unión (como type number = Int of int | Float of float) o por todo el boxeo. La siguiente solución utiliza el último enfoque:

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

Sin embargo, torres numéricos son prácticamente inútiles en el mundo real. A menos que usted es muy cuidadoso, tratando de aprender de estos tipos de desafíos borked va a hacer más daño que bien. Debe dejar preguntarse por qué no queremos una torre numérica, no quiere a la caja y no desea la promoción de tipo en tiempo de ejecución?

¿Por qué no acabamos de escribir:

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

saber el tipo de x a cada paso. Cada número está almacenado sin embalaje. Sabemos que este código nunca se producirá un error de tipo en tiempo de ejecución ...

Otros consejos

Estoy de acuerdo con Jon que este es un ejemplo muy artificial y no es un punto de partida bueno para aprender F #. Sin embargo, puede utilizar las limitaciones miembro estáticas para obtener razonablemente cerca y sin moldes y reflexión dinámicos. Si se marca como inline y añadir convertir tanto de los parámetros utilizando float:

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

Usted obtendrá una función con el siguiente tipo:

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

La función toma dos argumentos que se pueden convertir de forma explícita a flotar. La única limitación en comparación con la versión de LISP (supongo) es que siempre devuelve flotador (como el tipo numérico más universal disponible). Puede escribir algo como:

> 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

Definitivamente no es posible llevar a cabo esto con tipos estáticos adecuados. Usted puede decir que en Haskell, pero yo no le creo.

El problema con tratar de hacer esto con tipos estáticos es en la adición de dos números diferentes de diferentes tipos, posiblemente, preservando al mismo tiempo el tipo de la parte izquierda. Como Jon Harrop dice que esto es posible con un tipo de unión. Una vez que haya definido el tipo de unión y una operación de suma correspondiente, que funciona como se ha mencionado, el acumulador real es muy simple. Mi aplicación:

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"
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top