F#でArrow

練習中のF#(OCaml)でHaskellのArrow, ArrowChoice, ArrowApply, ArrowLoop を書いてみました。
突っ込みやアドバイス歓迎です。
ちなみにOCamlは空き時間などに携帯でWebサイトを見て勉強した(?)だけなので実はあまり自信が無い。。。(多少はPCでも書いたことあるけど


VS2010β入れてないので、F# CTPでプログラムを書きました。もしかしたら仕様変更の影響を受けてる部分とかあるかも・・・。

Arrow

(* F#特有の"light"の意味を良く分かってない。。。重くなさそうだ! *)
#light

let first f (x, y) = f x, y
let second f (x, y) = x, f y

let ( *** ) f g = first f >> second g
let (&&&) f g = (fun x -> x, x) >> (f *** g)

(* 出力結果は一番最後 *)
printf "first : %A\n" (first ((+) 1) (9, 5))
printf "second : %A\n" (second ((+) 1) (99, 99))

((+)1 *** (+)2) (9, 18) |> printf "*** : %A\n"
((+)20 &&& (+)30) 40 |> printf "&&& : %A\n\n"

ArrowChoice

(日記に載せる都合上分割してますが、プログラム的にはひと続きのファイルです。)

type 'a Either = Left of 'a | Right of 'a

let left f e =
    match e with
    | Left x -> Left (f x)
    | otherwise -> e
    
let right f e =
    match e with
    | Right x -> Right (f x)
    | otherwise -> e
    
let either f g =
    left f >> right g >> (function Left x | Right x -> x)

let (+++) f g = left f >> right g
let (|||) = either


(either ((+)1) ((+) 10)) (Left 99) |> printf "either : %A\n"

(Left 2.0, Right 2.0)
    |> ((sqrt ||| abs) *** (abs +++ sqrt))
        |> printf "|||, +++ : %A\n\n"

ArrowApply

let app (f, x) = f x

(first (+) >> app) (73, 27) |> printf "app : %A\n\n"

これだけです。。。

ArrowLoop

これめっちゃ苦労した!僕のへたれ脳には難度が高すぎてかなり悩んだ。(食卓にメモ用紙を持って行って、もぐもぐしながらプログラムをデッサンして考えまくった)
本題に入る前に遅延評価の確認。
OCamlとF#は若干この辺が異なる。

(* 岡村 *)
# let john = lazy (print_endline "john!john!john!!") ;;
val john : unit lazy_t = <lazy>
# Lazy.force john;;
john!john!john!!
- : unit = ()


(* F# *)
> let hanako = lazy (printf "i love u \n");;
val hanako : Lazy<unit>

> hanako.Force();;
i love u
val it : unit = ()
> hanako;;
val it : Lazy<unit>
= Microsoft.FSharp.Control.Lazy`1[Microsoft.FSharp.Core.Unit]
    {IsDelayed = false;
     IsException = false;
     IsForced = true;
     Value = null;}
追記(09/08/05)

F# CTPの時点では x.Force();; として評価していたけど、
VS2010βでは x.Value ;; で評価するようになっていた。(一応上のコードも動作するけど推奨されない)


では本題に戻ろう。
ArrowLoopは、Haskellだと以下のようにエレガントに定義できる。

loop f b = let (c,d) = f (b,d) in c

これがF#だと・・・

type 'a recc = In of ('a recc -> 'a)
let out (In x) = x

let loop f b =
    let a = (fun d -> let s:Lazy<'c> = snd <| f(b, (lazy (out d d))) in s.Force())
    in
        let (x:Lazy<'result>, _) = f (b, lazy(a (In a)))
        in x.Force()


(* 利用してみる、、、lazy & Force() のせいで美しくない。。。 *)
let loop_test = loop (fun (b, d) -> (lazy (d.Force() + 1)), lazy (b * 2)) 1
loop_test |> printf "loop1 : %A\n"

(* ArrowLoopを使ったY Combinatorの例 *)
let nList = loop (fun (b, d) ->
                    (lazy(d.Force() b), lazy(fun n -> if n=0 then [] else n::(d.Force() (n-1)))))
nList 10 |> printf "loop2 : %A\n"
追記(09/08/05)

これも先の追記同様で、遅延評価の部分が若干異なる。
書き直すと以下のようになる。

let loop f b =
    let a = (fun d -> let s:Lazy<'c> = snd <| f(b, (lazy (out d d))) in s.Value)
    let (x:Lazy<'result>, _) = f (b, lazy(a (In a)))
    x.Value

let loop_test = loop (fun (b, d) -> (lazy (d.Value + 1)), lazy (b * 2)) 1

let nList = loop (fun (b, d) ->
                    (lazy(d.Value b), lazy(fun n -> if n=0 then [] else n::(d.Value (n-1)))))

もっとエレガントに書ける技法とかあったら教えてください。。
どこで遅延評価を使うべきか、このλ式には何を渡すか、などという判断は先に終わっていたのだけど、最後はずっと型に苦しめられていた。Y Combinatorとかで遊ぶ時は動的型付け言語を使うことが多かったからなぁ。。。良い勉強になりました。



以下が上記のプログラムの出力。

first : (10, 5)
second : (99, 100)
[]*** : (10, 20)[]
&&& : (60, 70)

either : 100
[]|||, +++ : (1.414213562, Right 1.414213562)[]

app : 100

loop1 : 3
loop2 : [10; 9; 8; 7; 6; 5; 4; 3; 2; 1]
続行するには何かキーを押してください . . .


おしまい。