Domanda

Nella mia ricerca per saperne di più F #, ho cercato di realizzare un "generatore di accumulatore", come descritto da Paul Graham qui . La mia soluzione migliore finora è completamente dinamico digitato:

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"

Ho tre domande:

  • Se rimuovo il tipo di annotazione per x, il codice non riesce a compilare perché il compilatore deduce tipo int -> obj per x - anche se acc è indicata a restituire un obj->obj. Perché è questo e posso evitarlo?
  • Tutte le idee per migliorare questa versione digitato in modo dinamico?
  • E 'possibile perseguire questo obiettivo con tipi statici corrette? Magari con vincoli membri? (E 'possibile in Haskell, ma non in OCaml, per quanto ne so)
È stato utile?

Soluzione

  

Nella mia ricerca per saperne di più F #, ho cercato di realizzare un "generatore di accumulatore", come descritto da Paul Graham qui.

Questo problema richiede l'esistenza di una torre numerica non specificato. Lisp succede ad avere uno e sembra essere sufficiente per esempi di Paul Graham, perché questo problema è stato specificamente progettato per rendere Lisp aspetto artificialmente buona.

È possibile implementare una torre numerica in F # sia utilizzando un tipo di unione (come type number = Int of int | Float of float) o con la boxe tutto. La seguente soluzione utilizza il secondo approccio:

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

Tuttavia, torri numerici sono praticamente inutili nel mondo reale. A meno che non siete molto attenti, cercando di imparare da questi tipi di sfide borked non vi farà più male che bene. Si dovrebbe lasciare chiedendo perché non vogliamo una torre numerica, non si vuole scatola e non si vuole la promozione di tipo run-time?

Perché non ci basta scrivere:

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

Si conosce il tipo di x ad ogni passo. Ogni numero è memorizzato unboxed. Sappiamo che questo codice non produrrà mai un errore di tipo run-time ...

Altri suggerimenti

Sono d'accordo con Jon che questo è ad esempio molto artificiale e non è un buon punto di partenza per l'apprendimento F #. Tuttavia, è possibile utilizzare i vincoli membri statici per ottenere ragionevolmente vicino senza calchi dinamici e di riflessione. Se si contrassegna come inline e aggiungere convertire entrambi i parametri utilizzando float:

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

Si otterrà una funzione con il tipo seguente:

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

La funzione accetta ogni due argomenti che può essere convertito esplicitamente galleggiare. L'unica limitazione rispetto alla versione LISP (credo) è che restituisce sempre galleggiante (come il tipo numerico universale più disponibili). È possibile scrivere qualcosa di simile:

> 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

Non è sicuramente possibile implementare questo con tipi statici adeguati. È dire che si può in Haskell, ma io non ti credo.

Il problema con il tentativo di fare questo con tipizzazione statica è l'aggiunta di due numeri diversi di possibilmente diversi tipi preservando il tipo del lato sinistro. Come Jon Harrop dice che questo è possibile con un tipo di unione. Una volta definito il tipo di unione e una corrispondente operazione di addizione che funziona come detto, l'accumulatore reale è molto semplice. La mia applicazione:

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"
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top