f#のアキュムレータジェネレーター
-
30-09-2019 - |
質問
より多くのf#を学ぶための私の探求で、私はポール・グラハムが説明したように「アキュムレータジェネレーター」を実装しようとしました ここ. 。これまでの私の最良の解決策は、完全に動的にタイプされています:
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"
私には3つの質問があります:
- タイプの注釈を削除した場合
x
, 、コンパイラがタイプを導入するため、コードはコンパイルに失敗しますint -> obj
xの場合 - ただしacc
Anを返すように注釈が付けられますobj->obj
. 。なぜそれがあり、私はそれを避けることができますか? - この動的にタイプされたバージョンを改善するためのアイデアはありますか?
- 適切な静的タイプでこれを実装することは可能ですか?多分メンバーの制約がありますか? (Haskellでは可能ですが、OCAML、AFAIKでは可能です)
解決
より多くのf#を学ぶための私の探求の中で、ここでPaul Grahamが説明したように、「アキュムレータジェネレーター」を実装しようとしました。
この問題には、不特定の数値タワーの存在が必要です。 Lispはたまたま1つを持っていますが、Paul Grahamの例には適切であるため、この問題はLISPを人為的に見栄えを良くするように特別に設計されていたためです。
ユニオンタイプを使用してF#に数値タワーを実装できます( type number = Int of int | Float of float
)またはすべてをボクシングすることによって。次のソリューションでは、後者のアプローチを使用します。
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))
ただし、数値タワーは現実の世界では事実上役に立たない。あなたが非常に慎重でない限り、これらの種類のボルキングされた課題から学ぼうとすると、あなたは良いよりも害を及ぼすでしょう。なぜ私たちが数値のタワーを望まないのか、ボックスにしたくない、ランタイムタイプのプロモーションを望まない理由を自問する必要がありますか?
なぜ私たちは書いていなかったのですか:
let x = 1
let x = x + 5
ignore(3)
let x = float x + 2.3
私たちはタイプのタイプを知っています x
すべてのステップで。すべての数字が箱なしで保管されています。このコードがランタイムタイプのエラーを生成しないことを知っています...
他のヒント
これは非常に人工的な例であり、F#を学習するための良い出発点ではないことをジョンに同意します。ただし、静的メンバーの制約を使用して、動的なキャストや反射なしで合理的に近づくことができます。あなたがそれをとしてマークするなら inline
両方のパラメーターを使用して変換します float
:
let inline acc x =
let x = ref (float x)
fun y ->
x := (float y) + !x
!x
次のタイプの関数が得られます。
val inline acc :
^a -> ( ^b -> float)
when ^a : (static member op_Explicit : ^a -> float) and
^b : (static member op_Explicit : ^b -> float)
この関数は、フロートに明示的に変換できる2つの引数を任されます。 LISPバージョン(私が推測する)と比較した唯一の制限は、常にフロートを返すことです(利用可能な最も普遍的な数値タイプとして)。あなたは次のようなものを書くことができます:
> 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
適切な静的タイプでこれを実装することは間違いなく不可能です。あなたはハスケルでできると言いますが、私はあなたを信じていません。
静的なタイピングでこれを行こうとすることの問題は、左側のタイプを保存しながら、2つの異なる数の異なるタイプを追加することです。ジョン・ハロップが言うように、これは組合タイプで可能です。ユニオンタイプと、前述のように機能する対応する追加操作を定義すると、実際のアキュムレータは非常に簡単です。私の実装:
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"