ようこそ。睡眠不足なプログラマのチラ裏です。

F#で順列(Permutation)と組み合わせ(Combination)。YOU、Listモナドしちゃいなよ。集合モナドもあるよ。

以前、C#で順列(Permutation)と組み合わせ(Combination)をすべて列挙してみようという記事を書きました。
今見ると、前に思っていた以上に「しょっぱいなぁ」と思わざるを得ませんが、C#で書き直すつもりになれません。


今回は、超イケてる言語のF#で書きます。
そして、最近わたしがモナドにハマっているということもあり、非決定性計算を得意とするListモナドを利用したいと思います。


非決定性計算に役立つ Listモナド

Listモナドは非決定性を持つ計算について合成する戦略を持つモナドです。


ということなのですが、「非決定性計算」という言葉の意味するところを知らない場合、「お前は何を言っているんだ。」と思わざるを得ません。
一体どういうことを言っているのかというと、要は総当たりする何かの計算と思ってさしつかえないと思います。
つまり、あるリストの全要素について任意の計算を適用しながら、リストを合成して、新しいリストを作り出すための戦略がListモナドということになります。
モナドの性質をうまく利用していて、ループが複数ネストしてしまうような総当たり計算であっても、
あたかもフラットであるかのような宣言的な記述方法で、曖昧性を解決する計算を構築しやすくしてくれる。


C#VBプログラマであれば、LINQによる総当たり処理を思い浮かべてみるとイメージしやすいでしょう。
この件については、のいえさん(@neuecc)が、Linqと総当り - neue ccという記事を書いています。ぜひ参考にしてください。


Listモナドは大変便利なので、F#にもぜひ欲しい。


でもちょっと待って。そもそもF#にはシーケンス式があるよ

でも、ちょっと待ってください。
F#には、もともと シーケンス式 というナイスな構文が用意されているじゃないか。
なので「F#にListモナドなんて別にいらないんじゃね?」と思われるかもしれません。確かにそうかもしれません。
が、書き易さや可読性、メンテナンス性の観点から、私はコンピューテーション式でListモナドを用意しておきたい派です。


シーケンス式でFizzBuzzを書くとこう。

let fizzbuzz = 
  [for x in [1..100] -> 
    let f,b = "Fizz", "Buzz"
    match x % 3, x % 5 with
    | 0,0 -> f + b
    | 0,_ -> f
    | _,0 -> b
    | _   -> string x]

List.iter (fun x -> printfn "%A" x) fizzbuzz

これは、ループがひとつなので特に気になりません。


では、覆面算 SEND MORE MONEYを書いてみましょう。

let solve () =
  let digits = Set.ofList [0..9]
  let inline toInt xs  = List.fold (fun x y -> x * 10 + y) (0) xs
  [for s in digits - Set.singleton 0 do
     for e in digits - Set.singleton s do
       for n in digits - Set.ofList [s;e] do
         for d in digits - Set.ofList [s;e;n] do
           for m in digits - Set.ofList [s;e;n;d;0] do
             for o in digits - Set.ofList [s;e;n;d;m] do
               for r in digits - Set.ofList [s;e;n;d;m;o] do
                 for y in digits - Set.ofList [s;e;n;d;m;o;r] do
                   let send = toInt[s;e;n;d]
                   let more = toInt[m;o;r;e]
                   let money = toInt[m;o;n;e;y]
                   if send + more = money then
                     yield! [send; more; money]]

これはひどい。ネストが深くなってしまいました。




でも、実はインデントを下げる必要はなくて、

let solve () =
  let digits = Set.ofList [0..9]
  let inline toInt xs  = List.fold (fun x y -> x * 10 + y) (0) xs
  [for s in digits - Set.singleton 0 do
   for e in digits - Set.singleton s do
   for n in digits - Set.ofList [s;e] do
   for d in digits - Set.ofList [s;e;n] do
   for m in digits - Set.ofList [s;e;n;d;0] do
   for o in digits - Set.ofList [s;e;n;d;m] do
   for r in digits - Set.ofList [s;e;n;d;m;o] do
   for y in digits - Set.ofList [s;e;n;d;m;o;r] do
   let send = toInt[s;e;n;d]
   let more = toInt[m;o;r;e]
   let money = toInt[m;o;n;e;y]
   if send + more = money then
     yield! [send; more; money]]


と、上記のようにフラットに書くこともできます。とはいえ、決して書き易いとは言えませんし、
可読性やメンテナンス性の観点から言って欠点が多く、ある程度複雑なリストを表現するような場合は適さないと考えます。
シンプルなリストを構築する場合にシーケンス式はとても有用ですが、その他の場合ではちょっと扱いにくいでしょう。



F#で Listモナド

そこで、コンピューテーション式でListモナドを用意しておきます。


まず、Haskell での定義を。
Listモナドは型クラスMonadとMonadPlusのインスタンスとかなんとか。

instance  Monad []  where
    m >>= k  = concat (map k m)
    return x  = [x]
    fail s       = []

instance  MonadPlus []  where
    mzero =  []
    mplus = (++)


F#のコンピューテーション式で書いてみます。

type ListBuilder() =
  let concatMap f m = List.concat( List.map (fun x -> f x) m )
  member this.Bind (m, f) = concatMap (fun x -> f x) m 
  member this.Return (x) = [x]
  member this.ReturnFrom (x) = x
  member this.Zero () = []
  member this.Combine (a,b) = a@b

let list = ListBuilder()

とりあえずまぁこんなところでしょう。
より多くの機能が欲しければ、各メソッドを実装していけばよいかと。
ListモナドのBindがなぜ複数のネストしたループを表現することができるのかは、実装を見れば明らかですが、
よくわからないという場合は、「リストモナドの動作原理を考える」を参照するとよいかもしれません。



ということで、覆面算SEND MORE MONEYを、コンピューテーション式によるListモナドで。

let solve' () =
  let digits = [0..9]
  let inline toInt xs  = List.fold (fun x y -> x * 10 + y) (0) xs
  let inline (-) a b = a |> List.filter (fun x -> List.forall (fun y -> x <> y) b)
  list { let! s = digits - [0]
         let! e = digits - [s] 
         let! n = digits - [s;e]
         let! d = digits - [s;e;n]
         let! m = digits - [s;e;n;d] - [0]
         let! o = digits - [s;e;n;d;m]
         let! r = digits - [s;e;n;d;m;o]
         let! y = digits - [s;e;n;d;m;o;r]
         let send = toInt[s;e;n;d]
         let more = toInt[m;o;r;e]
         let money = toInt[m;o;n;e;y]
         if send + more = money then
           return! [send; more; money]}

とても書き易く可読性もよいですね。コンピューテーション式によるモナドなので、
モジュール性が確保されていて、シーケンス式のfor - doよりもメンテナンス性が高いです。
別途コンピューテーション式でListモナドを用意しておくことは意味のあることです。


Listモナドで順列(Permutation)と組み合わせ(Combination)

ということで、タイトルにあったように、Listモナドで順列(Permutation)と組み合わせ(Combination)を実装してみた。

F# Snippentsに投稿しました。
http://fssnip.net/6C

open System

type ListBuilder() =
  let concatMap f m = List.concat( List.map (fun x -> f x) m )
  member this.Bind (m, f) = concatMap (fun x -> f x) m 
  member this.Return (x) = [x]
  member this.ReturnFrom (x) = x
  member this.Zero () = []
  member this.Combine (a,b) = a@b
  member this.Delay f = f ()

let list = ListBuilder()

let rec permutations n lst = 
  let rec selections = function
      | []    -> []
      | x::xs -> (x,xs) :: list { let! y,ys = selections xs 
                                  return y,x::ys }
  (n, lst) |> function
  | 0, _ -> [[]]
  | _, [] -> []
  | _, x::[] -> [[x]]
  | n, xs -> list { let! y,ys = selections xs
                    let! zs = permutations (n-1) ys 
                    return y::zs }

let rec combinations n lst = 
  let rec findChoices = function 
    | []    -> [] 
    | x::xs -> (x,xs) :: list { let! y,ys = findChoices xs 
                                return y,ys } 
  list { if n = 0 then return! [[]]
         else
           let! z,r = findChoices lst
           let! zs = combinations (n-1) r 
           return z::zs }

let x4P0 = permutations 0 [1;2;3;4]
printfn "4P0 = %d" x4P0.Length
x4P0 |> Seq.iter (fun x -> printfn "%A" x)
Console.WriteLine ("-----") |> ignore

let x4P2 = permutations 2 [1;2;3;4]
printfn "4P2 = %d" x4P2.Length
x4P2 |> Seq.iter (fun x -> printfn "%A" x)
Console.WriteLine ("-----") |> ignore

let x4C0 = combinations 0 [1;2;3;4]
printfn "4C0 = %d" x4C0.Length
x4C0 |> Seq.iter (fun x -> printfn "%A" x)
Console.WriteLine ("-----") |> ignore

let x4C2 = combinations 2 [1;2;3;4]
printfn "4C2 = %d" x4C2.Length
x4C2 |> Seq.iter (fun x -> printfn "%A" x)
Console.ReadLine () |> ignore


コード短すぎワロスwww さすが俺たちのF#さん!!



笑わない数学者「5つのビリヤード玉問題」

笑わない数学者からの挑戦状
http://r27.jp/quiz/mathematical-goodbye/



さっそく利用してみる。笑わない数学者「5つのビリヤード玉問題」を解いてみましょう。

// 5つのビリヤード玉問題
let billiardsProblem = 
  let judge (xs:int list) = 
    let a,b,c,d,e = (xs.[0],xs.[1],xs.[2],xs.[3],xs.[4])
    [a;b;c;d;e]@
    [a+b;b+c;c+d;d+e;e+a]@
    [a+b+c;b+c+d;c+d+e;d+e+a;e+a+b]@
    [a+b+c+d;b+c+d+e;c+d+e+a;d+e+a+b;e+a+b+c]@
    [a+b+c+d+e] 
    |> List.sort 
  list { let! xs = permutations 5 [1..11] 
         if [1..21] = judge xs then          
           return xs}

billiardsProblem |> printfn "%A" 


実行結果

[[1; 3; 10; 2; 5]; [1; 5; 2; 10; 3]; [2; 5; 1; 3; 10]; [2; 10; 3; 1; 5];
 [3; 1; 5; 2; 10]; [3; 10; 2; 5; 1]; [5; 1; 3; 10; 2]; [5; 2; 10; 3; 1];
 [10; 2; 5; 1; 3]; [10; 3; 1; 5; 2]]

おまけ:集合モナド


Collections.Set<'T> クラス (F#)
http://msdn.microsoft.com/ja-jp/library/ee353619.aspx


Collections.Set Module (F#)
http://msdn.microsoft.com/ja-jp/library/ee340244.aspx


はい。あいかわらず機械翻訳が残念なことになっていますが、
上記のとおりF#では集合を扱うクラスとモジュールが用意されています。
Listだけじゃなく、集合もモナドになってたらいんじゃね?という単純すぎる発想です。
とはいえ、基本的にListで代用できてしまうので、ありがたみは少ししかないかもしれませんが…。


集合モナド

type SetBuilder() =
  let unionManyMap f m = Set.unionMany ( Set.map (fun x -> f x) m )
  member this.Bind (m, f) = unionManyMap (fun x -> f x) m 
  member this.Return (x) = Set.ofList x
  member this.ReturnFrom (x) = x
  member this.Zero () = Set.empty
  member this.Combine (a,b) = Set.union a b

let set = SetBuilder ()


Listモナドを集合用に単純に書き換えただけですね。
Listモナドで言うところのconcatMapがunionManyMapとなっています。
積集合や対称差のための関数や演算子を用意するなどして、モジュールを充実させるとより扱いやすくなるかも。


集合モナドと他の方法とで、非決定性計算の速度を比較してみる。

let getProcessingTime f = 
  let s = new System.Diagnostics.Stopwatch ()
  s.Start()
  let r = f ()  
  s.Stop ()
  r,s.Elapsed 

Console.WriteLine ("----- send + more = money -- for Set")
let solve () =
  let digits = Set.ofList [0..9]
  let inline toInt xs  = List.fold (fun x y -> x * 10 + y) (0) xs
  [for s in digits - Set.singleton 0 do
   for e in digits - Set.singleton s do
   for n in digits - Set.ofList [s;e] do
   for d in digits - Set.ofList [s;e;n] do
   for m in digits - Set.ofList [s;e;n;d;0] do
   for o in digits - Set.ofList [s;e;n;d;m] do
   for r in digits - Set.ofList [s;e;n;d;m;o] do
   for y in digits - Set.ofList [s;e;n;d;m;o;r] do
   let send = toInt[s;e;n;d]
   let more = toInt[m;o;r;e]
   let money = toInt[m;o;n;e;y]
   if send + more = money then
     yield! [send; more; money]]
printfn "%A" <| getProcessingTime solve

Console.WriteLine ("----- send + more = money -- Listモナド")
let solve' () =
  let digits = [0..9]
  let inline toInt xs  = List.fold (fun x y -> x * 10 + y) (0) xs
  let inline (-) a b = a |> List.filter (fun x -> List.forall (fun y -> x <> y) b)
  list { let! s = digits - [0]
         let! e = digits - [s] 
         let! n = digits - [s;e]
         let! d = digits - [s;e;n]
         let! m = digits - [s;e;n;d] - [0]
         let! o = digits - [s;e;n;d;m]
         let! r = digits - [s;e;n;d;m;o]
         let! y = digits - [s;e;n;d;m;o;r]
         let send = toInt[s;e;n;d]
         let more = toInt[m;o;r;e]
         let money = toInt[m;o;n;e;y]
         if send + more = money then
           return! [send; more; money]}

printfn "%A" <| getProcessingTime solve'

Console.WriteLine ("----- send + more = money -- Setモナド")
let solve'' () =
  let digits = Set.ofList [0..9]
  let inline toInt xs  = List.fold (fun x y -> x * 10 + y) (0) xs
  set { let! s = digits - Set.singleton 0
        let! e = digits - Set.singleton s
        let! n = digits - Set.ofList [s;e]
        let! d = digits - Set.ofList [s;e;n;]
        let! m = digits - Set.ofList [s;e;n;d;0]
        let! o = digits - Set.ofList [s;e;n;d;m]
        let! r = digits - Set.ofList [s;e;n;d;m;o]
        let! y = digits - Set.ofList [s;e;n;d;m;o;r]
        let send = toInt[s;e;n;d]
        let more = toInt[m;o;r;e]
        let money = toInt[m;o;n;e;y]
        if send + more = money then
          return [send; more; money]}

printfn "%A" <| getProcessingTime solve''

Console.WriteLine ("----- send + more = money -- Seq")
let solve''' () =
  let digits = Set.ofList [0..9]
  let inline toInt xs  = List.fold (fun x y -> x * 10 + y) (0) xs
  seq {for s in digits - Set.singleton 0 do
       for e in digits - Set.singleton s do
       for n in digits - Set.ofList [s;e] do
       for d in digits - Set.ofList [s;e;n] do
       for m in digits - Set.ofList [s;e;n;d;0] do
       for o in digits - Set.ofList [s;e;n;d;m] do
       for r in digits - Set.ofList [s;e;n;d;m;o] do
       for y in digits - Set.ofList [s;e;n;d;m;o;r] do
       let send = toInt[s;e;n;d]
       let more = toInt[m;o;r;e]
       let money = toInt[m;o;n;e;y]
       if send + more = money then
         yield! [send; more; money] }
printfn "%A" <| getProcessingTime solve'''

Console.ReadLine () |> ignore


実行結果(マシンスペック等はお察しください)

----- send + more = money -- for Set
([9567; 1085; 10652], 00:00:04.2887953)
----- send + more = money -- Listモナド
([9567; 1085; 10652], 00:00:02.1857583)
----- send + more = money -- Setモナド
(set [1085; 9567; 10652], 00:00:05.1224592)
----- send + more = money -- Seq
(seq [9567; 1085; 10652], 00:00:00.0014052)


「集合モナド遅せーじゃん。」って、なってしまうわけですが、それは問題の性質や領域によって変わってくるお話。
この覆面算については扱う集合があまりにも単純すぎて、リストで扱った方が高速になるが、
より複雑な集合問題を扱う場合は、集合モナドを利用した方がよりシンプルに書くことができて、高速に処理できます。たぶん。
まぁ、多くの場合はListモナドで事足りてしまうような気がするので、集合モナドの活躍の場はあんまりないかも(えっ。
そして一見、「seq が圧倒的パフォーマンスを見せつけているぞ!」と思うかもしれませんが、それはぜんぜん違うくて、
単に遅延評価が働いているだけです。今回の時間の計測方法では早いように見えるだけです。seq は IEnumerable ですからね。お間違えのないように。



ということで、Listモナドで順列(Permutation)と組み合わせ(Combination)を実装しなおしてみたら、
C#と比較にならないくらい短く書けましたよ!というご報告でした。俺達のF#がふつくしすぎる。


FizzBuzz問題から学ぶモナド

前回のエントリモナドについて熱く語りました。今回はゆるいモナドの雑記です。
モナドを利用してFizzBuzz問題を書いてみることで、「モナドってなんなん?なにしてくれちゃってるん?」ということが、
もしかしたら分かるかもしれないよ、という息抜き的なお話です。


FizzBuzz問題から学ぶモナド

FizzBuzzモナドで - トウフ日記
http://d.hatena.ne.jp/nskj77/20070512/1178952068


よりコードを引用します。

data FB a = FB (a, String)

instance Monad FB where
  return x = FB (x, "") 
  m >>= f  = bind m f 
    where bind (FB (x, y)) f = let FB (m, n) = f x in FB (m, y ++ n)

instance (Show a) => Show (FB a) where
  show (FB (x, "")) = show x
  show (FB (_, y))  = y 

fizzbuzz = mapM_ (\x -> print (fizz x >>= buzz))
  where fizz x | x `mod` 3 == 0 = FB (x, "Fizz")
               | otherwise      = return x
        buzz x | x `mod` 5 == 0 = FB (x, "Buzz")
               | otherwise      = return x


はい。FizzBuzz問題モナドで実装してみようというHaskellのコードです。
これは非常に面白いですね。さっそくF#へ移植してみましょう。

type FB<'T> = FB of 'T * string

let inline (>>=) (FB (x, y)) f = f x |> function | FB(m,n) -> FB(m, y + n)
let bindFB m f = m >>= f
let fbreturn x = FB x

type FizzBuzzBuilder () =
  member this.Return (x) = fbreturn x
  member this.Bind (m,f) = m >>= f

let fb = FizzBuzzBuilder ()

let fizz = function
| x when x % 3 = 0 -> fb { return x,"Fizz" }
| x -> fb { return x,"" }

let buzz = function
| x when x % 5 = 0 -> fb { return x,"Buzz" }
| x -> fb { return x,"" }

let printFB  = function 
  | FB(x,"") -> printfn "%A" x
  | FB(_,y)  -> printfn "%s" y

let fizzbuzz = Seq.iter (fun x -> (fizz x >>= buzz) |> printFB)

[1..100] |> fizzbuzz |> ignore
System.Console.ReadLine () |> ignore


とくになんの捻りもなく、ベタに移植。とりあえずFizzBuzzできました。
Bind(>>=)で、とある型'Tとstringなタプルを伝番させて、出力内容を構築する仕組みになっていますね。


で、気付いたんですが、これはモナド則を満たしていませんね。
当然、元ネタのHaskellのコードについても同じことが言えます。
元ネタは、確かに型クラスとしてのMonadを利用してFizzBuzzを実装してはいますが、
MonadインスタンスであるFBがモナド則を満たしていないので、厳密にはモナドによるFizzBuzzの実装にはなっていないんですね。
具体的には、モナド則1について満たされていないので、残念な感じなっています。




モナド則1

return x >>= f  ==  f x


ベタ移植できていませんでした。正しくはこうです。

type FB<'T> = FB of 'T * string

let inline (>>=) (FB (x, y)) f = f x |> function | FB(m,n) -> FB(m, y + n)
let bindFB m f = m >>= f
let fbreturn (x:int) = FB (x,"")

type FizzBuzzBuilder () =
  member this.Return (x) = fbreturn x
  member this.Bind (m,f) = m >>= f

let fb = FizzBuzzBuilder ()

let fizz = function
| x when x % 3 = 0 -> FB (x,"Fizz")
| x -> fb { return x }

let buzz = function
| x when x % 5 = 0 -> FB (x,"Buzz")
| x -> fb { return x }

let printFB  = function 
  | FB(x,"") -> printfn "%d" x
  | FB(_,y)  -> printfn "%s" y

let fizzbuzz = Seq.iter (fun x -> fizz x >>= buzz |> printFB)

[1..100] |> fizzbuzz |> ignore
System.Console.ReadLine () |> ignore

そして、元ネタのHaskellのコード同様に、これはモナド則を満たします。




では、モナド則1を満たすように書き直してみます。
モナド則を満たしつつ、単に関数適用するようなかたちに書き換えてみます。

type FB<'T> = FB of 'T * string 

let inline (>>=) (FB (x, y)) f : FB<'b> = f (x,y) 
let fbreturn x = FB x

type FizzBuzzBuilder () =
  member this.Return (x) = fbreturn x
  member this.Bind (m,f) = m >>= f

let fb = FizzBuzzBuilder ()

let fizz = function
| x when x % 3 = 0 -> fb { return x,"Fizz" }
| x -> fb { return x,"" }

let buzz = function
| x,s when x % 5 = 0 -> fb { return x, s + "Buzz" }
| x,s -> fb { return x,s }

let printFB  = function 
  | FB(x,"") -> printfn "%A" x
  | FB(_,y)  -> printfn "%s" y

let fizzbuzz = Seq.iter (fun x -> (fizz x >>= buzz) |> printFB)

[1..100] |> fizzbuzz
System.Console.ReadLine () |> ignore


モナドFizzBuzzを書くならこう書くのが自然ですね。
この例では、fizz関数で生成されたFBモナドにbuzz関数をBind(>>=)で適用することで、モナドが合成されていることがわかります。


どこが変わったのかと言えば、コンピューテーション式のBindの実装です。
修正前の実装では、Bind(>>=)で f を x に適用したあとに、ごにょごにょ(文字列を結合する操作を)していました。
これにより、モナド則1(左単位元)が満たされなくなっていたということです。
前回のエントリでも書きましたが、モナドは「ジェネリックな関数適用」と見なすことができました。
逆に言うと、Bind(>>=)で関数適用以上のことをしてしまうと、モナドとしての整合性がとれなくなってしまうということです。
このことから、Bind(>>=)時に関数適用の操作の範囲を超えてしまうような余計な操作はしてはいけないことがわかります。
「関数適用の操作の範囲を超えるような処理は、Bind(>>=)対象となるモナド側で構築する」のが作法ということになります。
この例では、fizz関数とbuzz関数内でやっていることがそれにあたります。


世界のナベアツ問題もモナド


FizzBuzz問題に類似する世界のナベアツ問題(もはや死語)についても、
同じ戦略を利用することができるので、同様に関数適用の操作をするだけのFizzBuzzモナドを使って書いてみましょう。

let aho = function
| x when x % 3 = 0 || (string x).Contains("3") -> fb { return x, string x + "あほ" }
| x -> fb { return x, string x + "" }

let ahan = function
| x,s when x % 8 = 0 -> fb { return x, s + "あはん" }
| x,s -> fb { return x,s }

let printNabeatsu  = function 
  | M(x,"") -> printfn "%A" x
  | M(_,y)  -> printfn "%s" y

let nabeatsu = Seq.iter (fun x -> (aho x >>= ahan) |> printNabeatsu)

[1..40] |> nabeatsu
System.Console.ReadLine () |> ignore


FizzBuzzモナドがやっていること自体が、単なる関数適用にすぎないため、そのまま流用することができました。



「がぶさんのかんがえたさいきょうのBLげんご、F#」をモナド

ぼくのかんがえたさいきょうのBLげんご、F# - Gab_kmのブログ
http://blog.livedoor.jp/gab_km/archives/1355595.html


サンプルとしてもう一つ、こちらも実装してみましょう。


ガブさん曰く、

今回できなかったこととして、他の人に「なんてものを見てしまったんだろうな感」を与えないようにしたいところ。
このキャッキャウフフを何かにくるんで、(精神的に)安全に扱う仕掛けが必要だ。
そう、それはまさにモナドの得意とするところ。
BLモナドの実装が次なるチャレンジになるだろう。


ということで、BLに特化したBLモナドの実装?と行きたくなるのかもしれませんが、
この場合も、戦略としてFizzBuzzモナドが利用できます。FizzBuzzモナドで実装してみましょう。

type SemeUke = Seme | Uke
let createBoy name seme uke a = 
  let say s nm line = 
    let s = if s <> "" then s + "\n" else s
    s + nm + "「" + line + "」"
  a |> function
  | (Seme,s)-> fb { return Uke, say s name seme }
  | (Uke,s) -> fb { return Seme, say s name uke }

let gab = createBoy <| "ガブ" 
                    <| "んー。やっぱりもっとクールに。ほら、こうできる。ねっ?(手を握りながら" 
                    <| "んっ。。。こうですか?/// もっと教えてほしいです。。。///"

let kyon = createBoy <| "きょん"
                     <| "このうさみみに触れたければ、軽やかにスレーブを使いこなして。ほら、もっと素直になって?"
                     <| "っっっ/// うさみみがぴょんぴょんしちゃうよ///"

let printBL  = function 
  | M(x,"") -> printfn "%A" "「・・・。」"
  | M(_,y)  -> printfn "%s" y

printfn "%s" "きょん×ガブ"
(Seme,"") |> kyon >>= gab |> printBL

printfn "%s" "\nガブ×きょん"
(Seme,"") |> gab >>= kyon |> printBL

System.Console.ReadLine () |> ignore


難なくモナドで実装することができました。FuzzBuzzモナドがとても抽象的な操作をしていることがわかります。
モナドは非常に柔軟で拡張性も高いので、もう一人BL対象を増やすことも容易です。

type SemeUke = Seme | Uke | Another
let createBoy name seme uke another a = 
  let say s nm line = 
    let s = if s <> "" then s + "\n" else s
    s + nm + "「" + line + "」"
  a |> function
  | (Seme,s)-> fb { return Uke, say s name seme }
  | (Uke,s) -> fb { return Another, say s name uke }
  | (Another,s) -> fb { return Seme, say s name another }

let gab = createBoy <| "ガブ" 
                    <| "んー。やっぱりもっとクールに。ほら、こうできる。ねっ?(手を握りながら" 
                    <| "んっ。。。こうですか?/// もっと教えてほしいです。。。///"
                    <| "きゃっきゃ"

let kyon = createBoy <| "きょん"
                     <| "このうさみみに触れたければ、軽やかにスレーブを使いこなして。ほら、もっと素直になって?"
                     <| "っっっ/// うさみみがぴょんぴょんしちゃうよ///"
                     <| "ウフフ"

let kusomiso = createBoy <| "くそみそ"
                         <| "ウホッ!いい男。やらないか"
                         <| "ん゛ん゛ん゛ん゛ん"
                         <| "マッスル!マッスル!"

let printBL  = function 
  | M(x,"") -> printfn "%A" "「・・・。」"
  | M(_,y)  -> printfn "%s" y

printfn "%s" "きょん×ガブ"
(Seme,"") |> kyon >>= gab |> printBL

printfn "%s" "\nガブ×きょん"
(Seme,"") |> gab >>= kyon |> printBL

printfn "%s" "\nガブ×きょん×くそみそ"
(Seme,"") |> gab >>= kyon >>= kusomiso |> printBL

printfn "%s" "\nくそみそ×きょん×ガブ"
(Seme,"") |> kusomiso >>= kyon >>= gab |> printBL

System.Console.ReadLine () |> ignore

で、何度もしつこく言っていますが、このFizzBuzzモナドでは単に関数適用をしているにすぎません。
このBL的な何かの処理を、モナドで実装すること自体に意味はありません。あくまでFizzBuzzモナドがやっていることに着目してください。



で、FizzBuzzモナドって結局何なの

ということを踏まえたところで、FizzBuzzモナドに注目してもっと抽象的に考えていきましょう。
判別供用体の FB<'T> ですが、 'Tはそもそもジェネリックな型なので、FB of 'T * stringである必要はないですね。
表現としては、 FB of 'T で十分に表現できます。


以下のように書き直せます。

type FB<'T> = FB of 'T 

let inline (>>=) (FB (x, y)) f : FB<'b> = f (x,y) 
let fbreturn x = FB x

type FizzBuzzBuilder () =
  member this.Return (x) = fbreturn x
  member this.Bind (m,f) = m >>= f

また、同様の理由でBind(>>=)についても同じことが言えるので、
FBの'T型について、タプルであることを明示する必要はありません。


以下のように書き直せます。

type FB<'T> = FB of 'T 

let inline (>>=) (FB x) f : FB<'b> = f x 
let fbreturn x = FB x

type FizzBuzzBuilder () =
  member this.Return (x) = fbreturn x
  member this.Bind (m,f) = m >>= f

これって、そもそもFBって名前である意味がないよね。


以下のように書き直せます。

type M<'T> = M of 'T 

let inline (>>=) (M x) f : M<'U> = f x
let mreturn x : M<'T> = M x

type MonadBuilder () =
  member this.Return (x) = mreturn x
  member this.Bind (m,f) = m >>= f


おやおや。なにかとても見覚えのある形になりましたね。
もうお気付きですね。「これって、モナドの基本形そのままやん!*1」 はいそのとおりです。
先ほどまでFizzBuzzモナドと呼んでいたものの正体は、実はモナドの基本形そのものだったんだよ!





※オチです




当然のことながら、そのままFizzBuzz(世界のナベアツ、BL的な何か)が動きます。

type M<'T> = M of 'T 

let inline (>>=) (M x) f : M<'U> = f x
let mreturn x : M<'T> = M x

type MonadBuilder () =
  member this.Return (x) = mreturn x
  member this.Bind (m,f) = m >>= f

let m = MonadBuilder ()

let fizz = function
| x when x % 3 = 0 -> m { return x,"Fizz" }
| x -> m { return x,"" }

let buzz = function
| x,s when x % 5 = 0 -> m { return x, s + "Buzz" }
| x,s -> m { return x,s }

let printFB  = function 
  | M(x,"") -> printfn "%A" x
  | M(_,y)  -> printfn "%s" y

let fizzbuzz = Seq.iter (fun x -> (fizz x >>= buzz) |> printFB)

[1..100] |> fizzbuzz
System.Console.ReadLine () |> ignore


ということで、FizzBuzz問題モナドで書いてみることで、
モナドが何をやっているのか」、あるいは「モナドジェネリックな関数適用だよね」
ということを、さも説明してしまったかのような雰囲気をかもし出してみました。何かの参考になれば幸いです。

*1:いわゆるIdentityモナド

快刀乱麻を断つモナド - F#とIOモナドとコンピューテーション式の奥義と


         ,. -‐'''''""¨¨¨ヽ 
         (.___,,,... -ァァフ|          あ…ありのまま 今 起こった事を話すぜ! 
          |i i|    }! }} //| 
         |l、{   j} /,,ィ//|       『F#でIOモナドっぽい何かを作ろうとしていたら、 
        i|:!ヾ、_ノ/ u {:}//ヘ        いつのまにか、全然別モノのLazyモナドをつくっていた。』 
        |リ u' }  ,ノ _,!V,ハ | 
       /´fト、_{ル{,ィ'eラ , タ人        な… 何を言ってるのか わからねーと思うが 
     /'   ヾ|宀| {´,)⌒`/ |<ヽトiゝ        おれも 何をされたのか わからなかった… 
    ,゙  / )ヽ iLレ  u' | | ヾlトハ〉 
     |/_/  ハ !ニ⊇ '/:}  V:::::ヽ        頭がどうにかなりそうだった… 
    // 二二二7'T'' /u' __ /:::::::/`ヽ 
   /'´r -―一ァ‐゙T´ '"´ /::::/-‐  \   素の「Lazy<'T>」の入れ子だとか 「unit -> 'T」の連鎖だとか 
   / //   广¨´  /'   /:::::/´ ̄`ヽ ⌒ヽ    そんなチャチな遅延合成じゃあ 断じてねえ 
  ノ ' /  ノ:::::`ー-、___/::::://       ヽ  } 
_/`丶 /:::::::::::::::::::::::::: ̄`ー-{:::...       イ  もっと恐ろしい言語指向プログラミングの 片鱗を味わったぜ… 


に至るまでの前置き(能書き)が無駄に長いです。
F#でIOモナドの話についてのみ書くつもりが、モナド全般の話になってしまいました。
それにしてもこれは一体誰向けの記事なのか、書いた本人もよくわからないんだぜ…(ごくり)。






プログラミングの世界のモナドとは、計算を組み合わせてより複雑な計算を構築するための、戦略の抽象のことを言います。モナドと言えばHaskellが有名ですが、モナドという抽象概念は言語依存のものではありません。



モナド関数プログラミングにおける戦略の抽象である。」と言いました。戦略には計算に応じてさまざまな種類があり、それぞれに名前、問題、適用可能性、特徴、結果などがあります。たとえば、Maybeモナドという名前の戦略がある。これは「失敗する可能性のある計算の合成」という問題に対する解決策を持っている。複数の失敗するかもしれない計算を合成し、ひとまとめの失敗するかもしれない計算として扱いたいような場合に適用することができる。「失敗したらその後の処理はしない」という特徴を持っている。結果として、入れ子が深くなりがちな失敗可能性のある計算の連鎖について、複雑な計算をよりシンプルに表現することができる。感のよい人はもう気づいているかもしれないが、「これって、もしかしてデザインパターン?」そう、「○○モナド」をそれぞれの問題領域を解決するためのパターンとして捉えることができます。そしてそのパターン全体に係る「モナド」は、それぞれの○○モナドに対して同じ枠組みを提供している“より高い抽象”と言うことができます。モナドは同じ枠組み(同じ概念)の下で、実にさまざまな問題領域に対して解決策を提示してくれます。つまり、関数プログラミンにおける「メタデザインパターン」あるいは「メタ戦略」、「メタ理論」がモナドであると捉えることもできます。この高次な概念は、もともと数学の一分野である圏論から来ています。なぜ圏論モナドなんていうプログラミングと何の関連もない数学的構造が、プログラミングのあらゆる問題の解決策を示していたり、多くのプログラミングパターンを説明することができるのかはわかりません。そこには深イイ理由があるのかもしれないし、ただの偶然なのかもしれない。ただこれだけは言える。「数学SUGEEEEEEEEEEE」と。



Haskellの「Monad」は、モナドの抽象概念を適用した単なる型クラスのひとつです。モナドはメタ戦略なので、他の言語でもモナドを表現することはできます。しかし、型クラスと同程度の表現力を持っていない限り、Haskellほど美しくモナドとその周辺事情を表現することはできません。HaskellMonadは型クラスによって非常に洗練された表現をされているので、モナドを勉強する上でとても参考になります。なにかを学ぶ場合、抽象的な話だけではわかりにくいので、具体的な例で考えてみると理解の助けとなります。Haskellの知識が少し必要ですが、id:kazu-yamamotoさんの「QAで学ぶMonad - あどけない話」の説明がすばらしくわかりやすいです。




モナドは難しい」、「モナドは象だ」、「モナドかわいいよモナド」、「このモナドの説明を書いた人はこんなモナドの説明を読んでいます」、「モナドはメタファーではない」、「モナにゃんぺろぺろ」、「圏論モナドこそ至高」など、実にさまざまな声を聞きます。Real World Haskellオライリー)には、「モナドを使っていると苛々します」という皮肉めいた記述が現れたりもします。




憧れの(闇の)カンスウガタゲンガー達が語る「モナドの本当の意味」を理解しようとすると、やはり圏論の概念やら定理やらの正確な理解が必要なのかもしれず。でも、その域に達するには、おそらく私を含めた一般的なプログラマには険しくも困難な道。出口の見えない試練に身を投じることになります。
しかし、多くのカンスウガタゲンガーは、圏論の本質的な意味やその定理に強いわけではなく。それにもかかわらず、彼らはモナドを使ったプログラムを書きます。小難しい概念を正確に理解することはできなくても、モナドを利用してメリットを享受することはできるんです。そしてモナドはとても便利なんです。実際のところ「モナド*1」さえわかっていればほとんどの場合は困りません。使っているうちにモナドの性質が徐々にわかってきて、いずれ立派なモナグラマ−になれるかもしれない。もしかすると、みんなに愛でられるような可愛いオレオレモナドを発明できるようになるかもしれない。

C++モナドに置き換えて音読してみましょう。そんな感じです。






単に関数プログラミングにおけるモナドの実用性あるいは利便性という意味だけで言うと、モナド則(モナドが守るべき3つのルール)を満たす基本演算の定義をすれば、あらゆる操作を同じ枠組みのもとでジェネリックに連鎖できる仕組みを利用することができる、ということに他ならない。モナドがプログラミング上で表現している抽象は、ジェネリックな関数合成と言いかえることもできる。関数合成とモナドの合成の違いを考えると、モナドジェネリックな合成であり、異なる型の値を持つことができる点で異なる。これはそれぞれのシグネチャを比較するとわかりやすい。


Haskell:関数合成の演算子

(.) :: (b -> c) -> (a -> b) -> (a -> c)


Haskell:Bind演算子

(>>=) :: Monad m => m a -> (a -> m b) -> m b


(>>=)ではm aやm bと表されているように、合成時に異なる型の値が関わることがわかる。


Bind演算子(>>=)は、ある関数で値になんらかの処理をして、その値を次の関数に値を受け渡たしていく。ここでのある関数というのは、もちろん(a -> m b)のことであり、a は前の関数が返したモナドから値を取り出したものである。どのようにモナドから値を取り出して次の関数の引数にするのかを決定するのがBind演算子(>>=)というわけ。より関数型っぽく言いかえると、どのように(a -> m b)をm aに適用するのかを決めるための定義がBind演算子(>>=)。



では最初の、先頭にあたるモナドは一体どこからやってくるのか?ということになる。そこで、モナドが定義しなければならないもうひとつの定義、returnが導出される。returnは、なんらかの値を新たなモナド型の値にする関数で、しばしばモナドに包むなんて表現されたりもする。


Haskell

return :: Monad m => a -> m a


シグネチャも御覧の通りで、何も難しいことはしていません。Haskellでは、return や Bind演算子(>>=)はただの関数ではなくて、Monadクラス(型クラス)のメソッドになっていて、これによってアドホック多相が使える。なので、モナドがどのように合成されるのかという実装の詳細を意識しなくてもよいようになっている。つまり、文法上は同じように(>>=)でモナドが合成されていたとしても、そのモナドの種類によって実際の合成の挙動は異なる。つまるところこれはオーバーロード (多重定義)のことを言っていて、F#ではコンピューテーション式を用いることで、似たようなことができる。


モナドがやっていることは、実は強力な関数合成の一種であって、実は複雑なことはなにもしていない。にもかかわらず、モナドが難しいと言われる理由は、モナドが関数合成とジェネリックとアドホック多相が絡み合う複合体として表現される抽象であるからかもしれない。加えて、Haskellにあらわれる「クラス」や「インスタンス」という用語が、一般的なオブジェクト指向のそれの意味とまるで異なることも一因かもしれない。こういった事情があるので、日頃から抽象的な設計手法やメタプログラミングにあまり馴染みのないプログラマが突然モナドに触れると火傷してしまうというのは致し方ないことだ。プログラミングの基礎的素養は必要だが、順を追って丁寧に学習していけば「モナドがなにをやってくれているのかを理解する」こと自体はそれほど難しいことではない。





憧れの(闇の)カンスウガタゲンガー達は、暇さえあればモナドの話をしています。彼らは三度の飯よりモナドを好みます。なぜ彼らはモナドに夢中になるのか。モナドはそんなにも魅力的な存在なのでしょうか。そしてモナドは一体なんの役立つのでしょう。モナドジェネリックな関数合成だと説明しました。しかし、実際に何の役に立つかはあまりピンとこないかもしれません。



モナドはメタ戦略だといいました。プログラミングにおいて戦略は重要なファクターです。関数プログラミングを構造化するための戦略を定義するのに極めて重要なツール、それがモナドです。モナドなくして関数プログラミングは語れません。モナドには、そう言い切れるそれを特に決定付けるいくつかの特徴があります。



(1)複雑な計算の可読性向上が期待できる。モナドは美しい!
計算を繋ぐBind演算子(>>=)によって、関数プログラミングを命令型的に表現することができる。モナド合成時の実装の詳細を意識しなくてもよいようになっているので、コードを劇的に単純化することができ、可読性の向上が見込めます。
 

HaskellのBind演算子(>>=)は、F#のコンピューテーション式(ビルダー)では

と表現されて、 シグネチャは、

です。



Bindの働きは、平たく言えばただの関数の結合ですが、通常の関数の合成を拡張した働きを持たせるのが一般的で、たとえば、Maybeモナドでは、成功と失敗を表す2種類のデータを次の関数へ伝達することができるようになっている。F#でoption型を利用することを考えた場合、Maybeモナドは欠かせないものとなります。→ F#プログラマのためのMaybeモナド入門 - みずぴー日記

ただし、モナドを利用することでコードの美しさが保てるかどうかは、実装言語によって大きく変わってくきます。たとえばHaskellには型クラスがあるのでとてもエレガントに表現されている。コードも非常に美しいです。F#ならコンピューテーション式できれいに表現できる。Scalaではfor式できれいに表現できる。他の言語だとまた状況は変わってきます。



(2)カプセル化とモジュール性を確保。頑丈なプログラミングを促進する!
モナドのBindによる合成においては、合成時の値がモナドの外に漏れ出ることがないのでモジュール性が保証されます。この特徴によって、プログラマモナド内でプログラムしたものが他の領域に干渉することを懸念する必要がないので、とても幸せです。これはモナドプログラミングの純粋さを示すものであり、計算そのものとその合成戦略を分離していることを意味しています。つまりモナドは安全で動的に組み合わせ可能な計算として機能します。

 
まったくの別物なので、誤解を招いてしまいそうで例えることを躊躇しそうになるが、オブジェクト指向プログラマであれば、動的に機能拡張が可能という意味では、GoFデザインパターンにおける Decoratorパターン をイメージすると、モナドの拡張可能なモジュール性というものを想像しやすいかもしれない(あくまでイメージで)。オブジェクト指向パラダイムのDecoratorパターンでは、オブジェクトとオブジェクトを組み合わせることで、新たに複雑なオブジェクトを生成していました。 関数プログラミングパラダイムでは、より高い抽象であるモナドでそれを高い次元で実現する。状況に応じてさまざまな用途のモナドを使い分けたり、必要に応じて複数のモナドを合成したりします。



(3)モナドによる柔軟性の高い関数型プログラミングは、アジャイル性を与える!
モナドは汎用なインターフェイスを持ち、関数の純粋さを保つ性質がある。これによりコードのリファクタリングが容易になり、また多くの場合は反復開発が容易になる(アジャイル性の向上)。モナドを用いない関数型プログラミングより、モナドを利用したプログラムはより柔軟性が高くなりやすい。これはモナドによる計算が、一連のプログラムとデータをカプセル化し、モジュール性があることからも説明できます。アジャイルというとオブジェクト指向とセットで語られることが多いが、真にアジャイル性を追及するならば、関数プログラミングを選択することがベターであることは確定的明らかである。








前ふりが長くなりましたが、やっとこの記事を書くきっかけとなったIOモナドの話をします。IOモナドは副作用をモナドの中に封じ込めて、純粋関数を表現するための仕組みを提供するパターンであり、非常に巧妙な仕組みです。少し言い換えると、副作用のある部分と副作用のない部分を明確に分離するための仕組みを提供する戦略を持っています。



この夏、「ATEND - 第0回 スタートHaskell」というイベントがあるそうです。定員150人にもかかわらず、すでに定員オーバーしています(すごい)。最近モテ言語と言われているだけあって、Haskell恐ろしい人気です。そちらのキャッチフレーズ(?)より以下を引用させていただきます。

スタートHaskell :: [初心者] → IO [経験者] -- この夏、Haskellを始めよう!

これが意味するところがそういう意味なのかはわかりませんが、IOモナドHaskellにおいて、脱Haskell初心者の鬼門のひとつとされているモナドのひとつで、とりわけ特殊な存在として捉えられています。しかし、「IOモナドモナドじゃないよ」「IOモナドなんて勉強しなくてもモナドは使えるよ」などと言う声もあります。それでいて魔術的な一面も持っていたり、IOモナドモナドの中でも他とは毛色の違う不思議な存在です。これは、HaskellのIOモナドの定義がプラットフォーム依存(評価器, 実行器依存)であることに起因します。Haskellの「純粋さ(参照透明性)」に密接な関わりをもっています。さらにHaskellは遅延評価を採用している言語であることも関係してきます。このような特徴を持つIOモナドは特別な存在です。したがって、設計思想の異なる他の言語でHaskellのIOモナドを真似ることは難しいでしょう。





IOモナドについて語るには、まずは副作用とは何なのかを知る必要がありました。


副作用とは、関数やメソッドなどのある処理(計算)単位が、外部の環境になにかしら影響を及ぼすことを言います。つまり、処理の中で論理的な状態を変化させて、それ以降で得られる結果になんらかの影響を与えることです。代表的な例としては、コンソールを使った入出力や変数への値の破壊的代入、UIへの描画などがあげられます。





副作用がなく一貫性のある処理単位を純粋関数と呼ぶ。


「純粋」という修飾は、つまりそれがコンポーザブルであることを意味していて、大きく2つの特徴を持っている。まず、「自己完結」しているという特徴がある。そのため、純粋関数は他との結び付きや相互依存を一切気にすることなく、関数を自由に合成することができる。そして、純粋関数は「状態を持たない」。純粋関数の値(計算結果, 返り値)は、常にその引数の値によって決定される。同じ引数を伴う関数の計算が、いついかなる場合も同じ値となる(いわゆる参照透明性)ときそれは純粋関数だ。外部の環境に対して副作用を伴うような関数を部品として扱うと、必ず危険を伴う(バグりやすい)。環境に依存していて、事あるごとに結果が異なるような関数を使うなんて正気の沙汰じゃない!という意見はもっともです。それに対して、「自己完結」していて「状態がない」という環境に依存しない特徴を持っている純粋関数は、コンポーサブルな安全な部品として扱える。


関数型言語でこそ、より意味のある純粋関数
関数を純粋にすると、外部環境を意識せずに済むので可読性が高くなります。状態を持たないので、コードのリファクタリングが容易にもなります。また、多くの場合は設計の変更に強く、反復開発が容易になりやすいでしょう(アジャイル性の向上)。他との依存性もないので単独でのテストも容易で、テストコードは関数を呼び出すことに集中できて、デバッグも容易になるなど良いことずくめ。この純粋関数の特徴をより高く抽象的に昇華されたのがモナドで、合成可能なメタ純粋関数として捉えることもできる。


純粋関数は、もちろんHaskellだけのものでもなければ、ScalaやF#などを含めた関数型言語だけのものでもない。Java, C#, VB.NETというような、一般的なオブジェクト指向言語での実装においても、副作用をともなわない純粋関数を書くことは常に意識されてきた。ただ、オブジェクト指向言語では、関数をファーストクラス(第一級の値, first-class)*2として扱えないため、関数を部品として扱えず、純粋関数が持つ特徴である「自己完結」していて「状態を持たない」ので構成可能で安全な部品として扱えるという最大のメリットを享受することができない。それに対して、関数型言語はコンポーサブルな純粋関数を自由に組み合わせることで、より大きな純粋関数を作り出して効果的に再利用することができる。モナドについても同様のことが言え、純粋関数よりもさらに強力さを持つ。これは「なぜ関数型で書くのか」の理由のひとつと言える。




初めて読んだHaskellの本「ふつうのHaskellプログラミング」にはこんなことが書いてありました。
IOモナドにはふたつの目的があって、まずひとつは「副作用を伴う処理(アクション)の評価順序を確実に指定する」こと。もつひとつは、「参照透明性を保ったまま副作用を実現する」ということでした。
あるいは、「IOモナドは、まだ入出力を実行していない世界をアクションの中に包み込んでいて、アクションが評価されると、入出力の結果と、入出力を実行した後の世界が生成されます。」というようなことも書いてありました。3年くらい前に読んで、なんとなくわかったような気になっていましたが実は何もわかっていませんでした。そのときはまだ他の関数型言語も学んだことがなく、Haskellにも触れたばかりだったので。とここに言い訳をしておく。今思えば、IOモナドからモナドを入門するのは、ちょっと難しいのではないかと思います。



IOモナドの目的ですが、まず、ひとつめの目的は、Haskellが遅延評価*3を特徴としている言語であるということが深く関係しています。なので、主にHaskellというプログラミング言語におけるIOモナドの立ち位置としての目的と言えるかもしれません。ふたつめの目的は、副作用のある計算を純粋関数で表現すること。ひいては、副作用のカプセル化とモジュール性の確保が目的であると言えます。Haskellでは、副作用を持ち込みながら参照透明性を保つ手段として、モナドの持つ特徴をうまく利用していて、IOモナドを一方向モナドとして扱い、実行器まで巻き込むことで副作用をうまく封じ込めることに成功している。実行器まで巻き込むということがあまり説明されていないことが多いので、初心者は路頭に迷ってしまうのだと思う。

一方向モナドの素晴しい機能は、プログラムのモナドではない部分の関数型の性質を破壊することなく、そのモナド演算子中で副作用をサポートすることができるというものです。

モナドのすべて」のモナド則-出口はないを参照されたい。



Haskellでは、すべての計算はトップレベルのIOモナドの中で起こります。実行器まで巻き込むというのはつまりそういう意味です。副作用は全てIOモナドにリフトされます。開発者が関数の外部環境に勝手にアクセスすることはできないようになっています。なので純粋です。「副作用のある計算を純粋関数で表現する」という、副作用と純粋関数の意味を考えると一見矛盾した要求に対して、モナドはみごとな解決策を与えているというわけです。IOモナドが一方向モナドであるため、純粋関数の役割を果たしていて、副作用を内包しながらも参照透明性が確保されている。これは実に巧妙というかなんというか、面白い。


F#やScalaなど副作用を許容する非純粋関数型言語においても、出来る限り参照透明性を保つようにプログラムを組むことが推奨される。非純粋関数型言語の「純粋さ」は開発者にゆだねられています。
IOモナドが副作用を構成可能な純粋関数としてくれるのならば、それは非純粋関数型言語屋にとっても朗報です。副作用をカプセル化してコンポーサブル化するということも、IOモナドの目的のひとつと言うことができるでしょう。



さて、無駄に長い前置きは以上です。





ここからが本題。


Haskellは純粋関数型言語です。副作用に対して厳格です。そして遅延評価を採用しています。よく訓練されたカンスウタガゲンガーは、副作用のある関数とない関数を分けて実装することを考えるそうです。しかし、それに依存したのでは、プログラムの純粋さを死守することはできません。そこで、HaskellではIOモナドというプラットフォーム依存のモナドを導入することでこの問題をエレガントに解決しました。言い方を変えると、Haskellでは、IOモナドによって副作用のある部分とない部分を完全に分けることをプログラマに強制させる仕組みを設けました。ゆえにHaskellは純粋でいられるのです。



先に「設計思想の異なる他の言語でHaskellのIOモナドを真似ることは難しいでしょう。」と述べました。F#でHaskellのIOモナドを完全模倣することはできません。が、「F#の立ち位置としてのIOモナド」ならば表現することはできなくはありません。


F#は非純粋関数型言語です。seq, asyncなどの組み込み済みのコンピューテーション式が提供されてはいますが、コンピューテーション式(によるモナドの実装)は基本的に自作してくださいというスタンスです。当然、IOモナドをはじめとしたHaskellで標準的に備わっているモナドはありません。
また、副作用に対しては比較的寛容で、プログラマのさじ加減ひとつでプログラムは純粋にも破壊的にもなります。


もちろん我々カンスウガタゲンガーは出来る限り純粋さを保つことを望んでいます。F#で開発する場合でも、副作用を適切に分けてプログラミングしたいという要求は当然あります。すべての副作用をIOモナドの中に封じ込めるいうのは、F#の場合あらゆる面で現実的ではありませんが、局所的に副作用をIOモナドによって管理することは、それなりに意味のあることです。なぜなら、よく訓練されたカンスウタガゲンガーは、副作用のある関数とない関数を分けて実装することを考えるから*4


以上のことを踏まえて、F#でIOモナドを模倣することを考えてみます。しかし、これには大きな問題がいくつかあります。ひとつは、F#がオープンソースであるとは言え、Haskellのように評価器、実行器まで巻き込むことはできないこと。この点に関しては即詰みです。なので評価器と実行器のことは忘れて、能動的に着火する必要がある関数としてIOモナドの表現を考えます。また、副作用を含む合成可能な純粋関数としてのIOモナド(副作用のコンポーサブル化)を実現することを考えます。




では、さっそくF#でIOモナドを書いてみよう!となるわけですが、その前に、F#のもっとも優れた機能のひとつであり、F#でモナドを書く時に必要不可欠であるコンピューテーション式について簡単に触れておきたい。


コンピューテーション式は、あるF#コードを独自の解釈でユーザー定義することができる機能です。
つまり、F#言語構造の意味解析をプログラマが上書きしちゃえるんです。いわゆる言語指向プログラミング(LOP)を可能としています。非常に強力で面白い機能です。コンピューテーション式は、F#でモナドを実装するのにとても便利です。ただ、モナドを書くためだけに存在するものではなく、ドメイン固有言語(DSEL)としての利用も一般的です*5。for, if, yield!, use!, try withなどの補助的なキーワードの再定義も充実していて、モナドをより扱いやすいように式をカスタマイズすることもできます。目的によっては、コンピューテーション式(ビルダー)がモナドである(モナド則を満たす)必要はありません。




namespace CompExpr.Monad
open System
open Microsoft.FSharp.Core.Operators.Unchecked 

[<AutoOpen>]
module Base =
  /// ComExprBase
  type ComExprBase () = 
    member this.Using (x : #IDisposable, f) =
      use x = x 
      f x
    member this.TryWith (f, handler) =
      try f ()
      with e -> handler e
    member this.TryFinally (f, final) =
      try f ()
      finally final ()

/// IOモナドモジュール
module IO = 

  /// IO判別共用体
  type IO<'T> = internal IO of 'T

  let (>>) (_ : IO<_>) f = f ()
  /// bind
  let (>>=) (IO(a)) k : IO<_> = k a  

  let ioBind m k = m >>= k
  /// return
  let ioreturn a = IO a
  /// zero
  let iozero = IO ()

  /// IOビルダー
  type IOBuilder() =
    inherit ComExprBase ()
    member this.Bind (m, k) = m >>= k
    member this.Return x = ioreturn x
    member this.ReturnFrom m = m
    member this.Zero() = iozero
    member this.Delay (f : unit -> IO<_>) = f
    member this.Combine (g, f) = g >> f
    member this.For (xs : #seq<_>, f : (_ -> IO<unit>)) =
      xs |> Seq.fold (fun _ x -> f x) iozero 
    member this.Yield (x) = ioreturn x
    member this.YieldFrom (x) = x
    member this.While (condition, (f : (_ -> IO<unit>))) =
      while condition() do
        f () |> ignore 
      iozero

  /// IOビルダーを生成します。
  let io = IOBuilder ()

  let iowrap f a = IO (f a)
  let iowrap2 f a b = IO (f a b)
  let iowrap3 f a b c = IO (f a b c)

  let putChar = printf "%c" |> iowrap
  let putStr  = printf "%s"  |> iowrap
  let putStrLn  = printfn "%s"  |> iowrap
  let ioignore = fun _ ->  iozero

  let getLine = System.Console.ReadLine |> iowrap
  let getKey = System.Console.ReadKey |> iowrap
  let getCurrentTime = io { return DateTime.Now } <| ()

  let liftM f m = io { let! x = m <| ()
                       return f x}

  let join (IO(IO(a))) = io{ return a }

  type Either<'T, 'U> =
    |Left  of 'T
    |Right of 'U
       
  //try :: Exception e => IO a -> IO (Either e a)
  let tryIO io =
    try Right ((io:unit -> IO<_>) <| ())
    with e -> Left (id e)

  //catch :: Exception e => IO a -> (e -> IO a) -> IO a
  let catchIO io f = 
    try (io: unit->IO<_>) <| ()
    with e -> f e

  //finally :: IO a -> IO b -> IO a
  let finallyIO ioa iob = 
    try (ioa:unit -> IO<_>) <| ()
    finally (iob:unit ->IO<_>) <| () |> ignore

  let tryCatchFinallyIO io fio =
    try
      try Right ((io:unit->IO<_>) <| ())
      with e -> Left e
    finally
      (fio:unit -> IO<_>) <| () |> ignore

  let getContents =
    seq { let condition = ref true
          while !condition do
            let line = System.Console.ReadLine()
            if line = null then 
              condition := false
            yield fun () -> ioreturn line}

  let interact f = io { for x in getContents do
                          let! x = x <| ()
                          putStr (f x) |> ignore } 


とりあずではありますが、IOモナドっぽい何かと、その周辺機能の一部を実装しました。モナド則も満たしています。着火タイミングについては、IOモナドの合成結果をunit -> IO<'T> にすることでプログラマが制御できるようにしています。また、コンピューテーション式の特徴を活かしてビルダー内で、if, for, use!, try with, try finally などが利用できるようにしました。



let hello = io { return! putStrLn "Hello,World!" }

let inout = io { let! s = getLine ()
                return! putStrLn s }
      
let h2 = 
  io { do! hello ()
       do! hello () 
       return "こんにちは、世界!"} 

let h3 = 
  io { let! s = h2 ()
       do! hello () 
       putStrLn s |> ignore
       do! inout ()
       do! hello ()
       return "さようなら" } 
      
let fire = 
io { let! s = h3 ()
        do! hello ()
        return s } 

fire () |> ignore
()

実行結果

Hello,World!
Hello,World!
Hello,World!
こんにちは、世界!
やっほー
やっほー
Hello,World!
Hello,World!


IOモナドであるinout内のgetLineで入力を求められるので、「やっほー」と入力します。すると、実行結果は上記のようになります。とりあえずOKですね。「さようなら」はignoreで捨てられました。



IOモナドの処理順序
モナドは評価順序を決定するという特徴をもっていました。それは遅延評価の場合のみ関係してくるわけではありません。それを確認するために、forld(foldl)とfoldBack(foldr)で、IOモナドの合成順序を逆にしても評価順序に変化がないことを確認してみましょう。


let aiueo = [io { return! putStrLn "あ"}; 
             io { return! putStrLn "い"};
             io { return! putStrLn "う"};
             io { return! putStrLn "え"}; 
             io { return! putStrLn "お"}]
                                      
aiueo |> Seq.iter (fun x -> x () |> ignore) 

let compose a b = fun () -> ioBind (a()) b
Console.WriteLine ("-----foldしても") |> ignore
let aiueo'' = List.fold (fun (a:unit->IO<_>) b -> compose a b) (fun ()->iozero) aiueo
aiueo'' () |> ignore

Console.WriteLine ("-----foldBackしても") |> ignore
let aiueo' = List.foldBack (fun (a:unit->IO<_>) b -> compose a b) aiueo (fun ()->iozero) 
aiueo' () |> ignore
Console.WriteLine ("同じ結果になるよね?") |> ignore

実行結果

あ
い
う
え
お
-----foldしても
あ
い
う
え
お
-----foldBackしても
あ
い
う
え
お
同じ結果になるよね?


はい。いずれも同じ結果になりますね。Haskellの場合はさらに遅延評価も絡んでくるのでまた事情がちょっと変わってきますが、正確評価のF#において、IOモナドの処理順序が確定的に明らかになっていることが確認できました。



IOモナドの無限リスト
IOモナドによって副作用を包み込むことで、副作用をあたかもファーストクラスとして扱えるようになりました。つまり、副作用を含む関数をリストに格納できることを意味します。IOモナドで、副作用の無限リストを表現してみましょう。

let hello = io { return! putStrLn "Hello,World!" }
let infiniteHello = Seq.initInfinite (fun x -> hello)
infiniteHello |> Seq.iter (fun x -> x () |> ignore) 


Hello,World!フォーエバー…!!



5件だけ処理してみます。

let hello = io { return! putStrLn "Hello,World!" }
let infiniteHello = Seq.initInfinite (fun x -> hello)
infiniteHello |> Seq.take (5) |> Seq.iter (fun x -> x () |> ignore) 


実行結果

Hello,World!
Hello,World!
Hello,World!
Hello,World!
Hello,World!


たいへんよくできました。



IOモナドと他のモナドを合成したい。
モナドを利用してプログラムを組んでいると、複数のモナドを組み合わせて扱いたくなるケースが多々でてきます。さまざまな領域の問題に対処するために必要なことです。例えば、IOモナドの中で「失敗するかもしれない計算」を扱いたいなんてことは、比較的多い要求のように思います。試しにIOモナドとMaybeモナドを組み合わせて利用してみましょう。

let fire = 
  io { 
    let! iom =  
      io {let! gl = getLine () 
          return maybe { return gl }
          } <| ()
    match iom with
    |Some x -> if x <> "" then 
                 putStrLn (x + x) |> ignore
               else 
                 putStrLn "入力してよねッ!ぷんぷくり〜ん(怒"  |> ignore
    |None -> ()}

fire () |> ignore


F#の立ち位置としてのIOモナドでは、上記のようにコンピューテーション式をネストして記述するかたちで、比較的簡単にモナドを合成することができます。ただ、ちょっと不吉なにおいがします。この程度のネストであれば許容範囲内かもしれないが、式が複雑化してくると可読性を犠牲にしてしまいそう。地獄のモナド「F#でモナドないわー、コンピューテーション式の多重入れ子ないわー、マジでないわー」と苛々しちゃうかもしれません。



そういえば、Haskellにはモナドを合成するための仕組みを持つ、モナド変換子というトランスレータがありました。IOモナドとMaybeモナドを合成するために、モナド変換子 MaybeTをちょっと真似てみましょう。




まず、合成対象のMaybeモナド

module Maybe = 
  open IO
  let bind x k =
      match x with
      | Some value -> k value
      | None   -> None

  let mreturn x = Some x

  type MaybeBuilder() =
    inherit ComExprBase ()
    member this.Bind(x, k) = bind x k
    member this.Return(x)  = mreturn x
    member this.ReturnFrom(x) = x
    member this.Delay(f)   = f()
    member this.Combine(a, b) = 
      if Option.isSome a  then a else b () 
    member this.Zero() = None
    member this.For(inp,f) =
        seq {for a in inp -> f a}
    member this.Yield(x) = mreturn x
    member this.YieldFrom(x) = x

  let maybe = new MaybeBuilder()

  let liftM f m = maybe { let! x = m
                          return f x}

そしてモナド変換子 MaybeT

module Maybe = 

......
 
  module Trans = 
 
    /// Maybeモナド変換子判別共用体
    type MaybeT<'T> = internal MaybeT of 'T

    /// bind
    let (>>=) (MaybeT(a)) k : MaybeT<_> = k a  

    let bindMaybeT m k = m >>= k
    /// return
    let maybeTreturn a = MaybeT a
    /// zero
    let maybeTzero = MaybeT ()
 
    /// Maybeモナド変換子ビルダー
    type MaybeTBuilder () =
      inherit ComExprBase ()
      member this.Bind(m, k) = m >>=  k
      member this.Return(a)  = maybeTreturn a
      member this.ReturnFrom(m) = m
      member this.Delay(f)   = f()
      member this.Combine(a, b) = 
        if Option.isSome a  then a else b () 
      member this.Zero() = maybeTzero
      member this.For(inp,f) =
          seq {for a in inp -> f a}
      member this.Yield(x) = maybeTreturn x
      member this.YieldFrom(x) = x

    // Maybeモナド変換子ビルダー
    let maybeT = new MaybeTBuilder ()

    /// MaybeTMonadIOのインスタンスだとかなんとか
    /// IO a をMaybeT IO aに持ち上げます
    let liftIO (m:IO<_>) = maybeT {return m}

    /// MaybeTMonadのインスタンスだとかなんとか
    let liftM f m = maybeT { return f m }

    /// コンストラクター
    /// MaybeT IO a -> IO (Maybe a)
    let runMaybeT (MaybeT(IO(a))) = 
      io { let! r = io { return maybe { return a } } <| () 
           return r}

module Unsafe =
  module IO =
    open IO
    /// 危険! (>_<)
    let unsafePerformIO (IO(a)) = a

MaybeTを使ってみます。

let fire = 
  io { 
    let iomaybe = Maybe.Trans.liftIO (getLine ()) |> runMaybeT <| ()
    let! x = iomaybe 
    match x with
    |Some x -> if x <> "" then 
                 putStrLn (x + x) |> ignore
               else 
                 putStrLn "入力してよねッ!ぷんぷくり〜ん(怒"  |> ignore
    |None -> ()}

fire () |> ignore


MaybeTを利用することで、IOモナドとMaybeモナドの合成をとてもシンプルに記述できました。それに、「ここでモナドを合成しているんだな!」ということが一目瞭然でわかりやすくなります。




F#で遅延評価をするには、unit -> 'T で処理の実行を遅らせる。あるいは System.Lazy<'T>によって、遅延評価をカプセル化します。つまり、遅延評価の処理を合成したい場合は、lazy関数の中で合成したいすべてのSystem.Lazy<'T>に対してForce()メソッドを呼び出せばよいということになります。

let result = 
  let a = lazy ("Hello,")
  let b = lazy ("World!")
  let h = lazy (a.Force() + b.Force()) 
  lazy ([for i in 0..4 -> h.Force()])

Seq.iter (printfn "%s")  <| result.Force()

たとえば、上記のような感じでlazyな関数は簡単に合成することができます。しかし、合成する際に必ず Force() で着火して評価しなればならず、着火漏れはバグの温床になりますし、見た目的にもどこか不恰好です。また、List.foldなどで、lazyな関数を合成したいような場合にも不便さを感じます。


F#のlazyは、System.Lazy<'T>型であらわされる遅延計算される値をカプセル化したものです。Lazy<'T>は遅延する値の入れ物とみなすことができます。ここで思い出されるのがモナド、あるいはコンピューテーション式です。モナドジェネリックな関数合成を表現できて、どのように合成されるのかという実装の詳細を意識しなくてもよいという特徴がありました。ある入れ物に対して、モナド則を満たすようなBindとreturnを定義すれば、それはもう立派なモナドです。遅延評価な値を合成するための戦略を自作するとよさそうです。つまりオレオレモナドを作ることでこの不便さを解消できそうです。



とりあえず、基本的な実装をしてみます。以下のようになります。

namespace CompExpr.Monad

module Lazy =

  type LazyBuilder () =
    let force (x : Lazy<'T>) = x |> function | Lazy x -> x
    /// bind
    let (>>=) (x:Lazy<'a>) (f:('a -> Lazy<'a>)) : Lazy<'a> =
      lazy (x |> force |> f |> force)  
    /// zero
    let lazyzero = Seq.empty
    
    member this.Bind(x, f) = x >>= f
    member this.Return (x) = lazy x
    member this.ReturnFrom (x) = x
    member this.Zero() = lazyzero 
    member this.Delay f = f()

  let lazyz = LazyBuilder () 

  let lifM f (Lazy(a)) = lazy f a   

利用側の実装サンプル

open System
open CompExpr.Monad.Lazy 

let result = 
  lazyz { let! a = lazy ("Hello,")
          let! b = lazy ("World!")
          return a + b}

result.Force() |> (printfn "%s") |> ignore
Console.ReadLine () |> ignore


実行結果

Hello,World!


で、とりあえずF#でコンピューテーション式として利用できるLazyモナドをつくることができたのですが、これだけだと利用する側は実はあまりうれしくありません。確かに、lazyな関数を合成する際に、Force ()メソッドを明示的に呼び出す部分を隠ぺいできましたが、この実装では単純な式を構築する場合にしか利用できません。lazyな関数をもっと直観的でかつ自由に組み立てることができる仕組みが欲しくなってきます。コンピューテーション式はそれができます。たとえば、コンピューテーション式内で、if式, for式, yield などを用いて遅延評価を合成しながら組み立てられるようにすると便利そうです。つまり、ドメイン固有言語(DSL)としての側面も持たせることで、さらに便利さが増します。





奥義とか言っちゃうと胡散臭さを禁じえませんが、あえてそう言い切っちゃいました。お察しください。



コンピューテーション式でドメイン固有言語(DSL)を作った経験をお持ちの方はおわかりの通り、
重要となってくるのは、Combine と Run をいかに使いこなすかということです。もちろん、コンピューテーション式で再定義可能なキーワードおよび、それに対応するすべてのメソッドが重要ですが、
コンピューテーション式を上手に使いこなす上で特に重要となってくるのが以下の2つメソッドです。



Combineメソッド



その名の通り式の「結合」を定義するためのものです。コンピューテーション式内の式の流れを統制するような役割を持っています。左から流れてくる式(式の境界線の上)を、右の式へ(式の境界線の下)受け流すというように、ムーディー勝山(消)をイメージすると理解しやすいです。さまざまな異なる式の変換をオーバーロード(多重定義)することで、柔軟な式を表現することができるようになります。ムーディー勝山は冗談なので忘れてください。




Runメソッド



これはコンピューテーション式の変換規則に明示されてはいませんが、ビルダークラス内で定義すると特殊な解釈がされます。Runメソッドを定義すると、builder {...}というコンピューテーション式を宣言すると同時にDelayメソッドが呼び出されます。つまり、最終的なビルダーの結果を決定づける定義を持たせることができ、最終的なビルダーの値を適切な型に変換する仕組みを提供していると解釈できます。Combineメソッドで扱っている式の流れにあわせて、最終的な式の結果を統一するのに役立ちます。もちろんオーバーロード(多重定義)可能です。Runメソッドの詳細については、The F# Draft Language Specification(http://goo.gl/7a1QH)を参照されたい。




F# Snippets(http://fssnip.net/)に投稿したものとして、冒頭でも紹介しましたが、LazyBuilderを作りました。


これは、ConbineとRunを効果的に利用した具体的な実装サンプルです。CombineおよびRunを多重定義することで、lazyな関数の合成を自然にコンピューテーション式内で表現可能にしています。また、IOモナドも扱えるように、F# Snippetsへ投稿したものに一部機能を追加しています。

namespace CompExpr.Monad
open CompExpr.Monad.IO 

module Lazy =

  type LazyBuilder () =
    let force (x : Lazy<'T>) = x |> function | Lazy x -> x
    /// bind
    let (>>=) (x:Lazy<'a>) (f:('a -> Lazy<'a>)) : Lazy<'a> =
      lazy (x |> force |> f |> force)  
    /// zero
    let lazyzero = Seq.empty

    let ioToLazyIgnore (xs:seq<Lazy<(unit -> IO<'T>)>>) = 
      xs |> Seq.reduce (fun (Lazy(a)) (Lazy(b)) -> 
            lazy (fun () -> (ioBind (a()) (fun x -> b()))) ) 
         |> fun (Lazy(x)) -> lazy (x() |> ignore)

    let ioToLazy (xs:seq<Lazy<(unit -> IO<'T>)>>) = 
      xs |> Seq.map (fun (Lazy(a)) -> a) |> fun x -> lazy (x)
    
    member this.Bind(x, f) = x >>= f
    member this.Return (x) = lazy x
    member this.ReturnFrom (x) = x
    member this.Zero() = lazyzero 
    member this.Delay f = f()

    member this.Combine (a,b) = 
      Seq.append a b 

    member this.Combine (a,b) = 
      Seq.append (Seq.singleton a) b

    member this.Combine (a,b) = 
      Seq.append a (Seq.singleton b) 

    member this.Combine (Lazy(a), Lazy(b)) = 
      lazy Seq.append a b 

    member this.Combine (a:Lazy<'T>, b:Lazy<seq<'T>>) = 
      (a,b) |> function | (Lazy x, Lazy y) -> lazy Seq.append (Seq.singleton x) y

    member this.Combine (a:Lazy<seq<'T>>, Lazy(b)) = 
      a |> function | Lazy x -> lazy Seq.append x (Seq.singleton b) 

    member this.Combine (a:Lazy<seq<'T>>, b:seq<Lazy<'T>>) =
      let notlazy (xs:seq<Lazy<'T>>) = xs |> Seq.map (fun (Lazy(a)) -> a)
      a |> function | Lazy x -> lazy Seq.append x (notlazy b) 

    member this.Combine (a:seq<Lazy<'T>>, b:Lazy<seq<'T>>) =
      let notlazy (xs:seq<Lazy<'T>>) = xs |> Seq.map (fun (Lazy(a)) -> a)
      b |> function | Lazy x -> lazy Seq.append (notlazy a) x  

    member this.For (s,f) =
      s |> Seq.map f 

    member this.Yield x = lazy x
    member this.YieldFrom x = x

    member this.Run (x:Lazy<'T>) = x
    member this.Run (xs:seq<Lazy<unit>>) = 
      xs |> Seq.reduce (fun a b -> this.Bind(a, fun _ -> b)) 
    member this.Run (xs:seq<Lazy<'T>>) = 
      xs |> Seq.map (fun (Lazy(a)) -> a) |> fun x -> lazy x

    member this.Run (xs:seq<Lazy<(unit -> IO<unit>)>>) = 
      xs |> ioToLazyIgnore
    member this.Run (xs:seq<Lazy<(unit -> IO<string>)>>) = 
      xs |> ioToLazy
    member this.Run (xs:seq<Lazy<(unit -> IO<int>)>>) = 
      xs |> ioToLazy
       
  let lazyz = LazyBuilder () 

  let liftM f (Lazy(a)) = lazy f a  
  let liftIO (Lazy(a)) = IO a


例1

let result = 
let h = 
  lazyz { let! a = lazy ("Hello,")
          let! b = lazy ("World!")
          return a + b}
  lazyz { for i in 0..4 do yield! h }
 
Seq.iter (printfn "%s")  <| result.Force()


実行結果

Hello,World!
Hello,World!
Hello,World!
Hello,World!
Hello,World!


例2
CombineとRunを実装しているので、以下のように書くことができる。

let lazyp n = lazy (printfn "%d" n)
let result2 = 
  let a = 
    lazyz {
        yield! lazyp 2
        ()}

  lazyz {
    yield! lazyp 0
    yield! lazyp 1
    yield! a
    for i in 3..5 do
      yield! lazyp i 
    yield! lazyp 6
    yield! lazyz {
              yield! lazyp 7
              yield! lazyp 8
              ()}
    for i in 9..10 do
      yield! lazyp i 
    yield! seq {for n in [11..15] -> lazyp n}
    yield! lazyz {
              yield! lazyp 16
              yield! lazyp 17
              ()}
    yield! lazyz {
              yield! lazyp 18
              yield! lazyp 19
              ()}
    yield! lazyp 20
    ()  }

result2.Force() 


実行結果

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


例3
せっかくなのでIOモナドを利用したい。

let iom1 = 
  io { 
    return! getLine ()
    }

let iom2 = 
  io {
    let! s = iom1 ()
    return s}

let iom3 = 
  io {
    let! s = iom2 ()
    return! putStrLn s} 

let result1 = 
  lazyz {
    yield iom3
    yield iom3
    yield iom3
    ()
    }

let ioResult1 = io { return result1.Force()}
ioResult1 () |> ignore


let iom5 = 
  io {
    let! s = iom1 ()
    putStrLn s |> ignore
    return s}

let result2 = 
  lazyz {
    yield iom5
    yield iom5
    yield iom5
    ()
    }

let ioResult2 = result2.Force()
let io6 = 
  io{ 
      let xs = Seq.map (fun x -> x) ioResult2
      for x in xs do
        let! s =  x()
        putStrLn s |> ignore 
      return () 
  }

io6 () |> ignore
Console.ReadLine () |> ignore

まとめ


モナドはさまざまなプログラミングの問題に適用できるメタ戦略である。圏論からきてるらしいけど、ただ使うぶんには小難しい理論は割とどうでもいい。
モナドを理解するには、ある程度プログラミングの素養が必要だが、とんでもなく難しいというわけではない。無意識に使っていることさえある。
・ある問題に対するモナドの適用可能性については、モナドジェネリックな関数合成として捉えるとイメージしやすい。
・F#の立ち位置としてのIOモナドは、「副作用のある関数とそうでない関数を切り分けて管理したい」という要求に対してはある程度意味のあることだ。
・コンピューテーション式でモナドを表現することができる。さらにそれを柔軟なものとする仕組みを持っている。
・コンピューテーション式のCombineとRunを効果的に使えるようになると、利便性の高いモナドあるいはDSELを作れるようになる。要チェックや。



お疲れさまでした。


追記:やばい。モナドって実は空気だった。

わたしはF#において、前方パイプライン演算子 (|>)はいい意味で空気のような存在だと思ってます*6

(* F# *)
(>>) : ('a -> 'b) -> ('b -> 'c) -> ('a -> 'c)
(|>) : 'a -> ('a -> 'b) -> 'b

-- Haskell
(.) :: (b -> c) -> (a -> b) -> (a -> c)
($) :: (a -> b) -> a -> b
(=<<) :: Monad m => (a -> m b) -> m a -> m b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)


なるほど。($)演算子に似ているのは(=<<)の方だったかー。そして、(>>=)はF#の(|>)に似ているという事実。これつまり、モナドって空気だったんだ!(えっまじで?)みたいなアハ体験。そもそも、MonadはApplicative(適用可能)だったやん!ということに気づかされたのでありました。「ジェネリックな関数合成」というよりも「ジェネリックな関数適用」の方がより適切な表現でしたね。まぁイメージとしては大きくははずしていないと思うし、説明するための用語的には「関数合成」でもそんなにわるくはないとは思います。でも、これからは「関数適用」に改めたいと思います。本文中はそんな感じで脳内補完しつつ読みかえていただけると良いかなと思います。


いげ太先生ありがとう!

*1:モナドが満たすべき3つの法則

*2:数値や文字列のような基本的な値と同じように扱うことができる値

*3:値が実際に必要となるまで式の評価を遅らせる

*4:どうしてもすべての副作用をIOモナドの中に封じ込めたい!と言うのであれば、Haskellのような純粋関数型言語の利用をご検討ください。

*5:ただし、ほとんどの場合はモナドにしておいた方が便利で、きっと幸せになります。

*6:パイプライン演算子が好きすぎてC#で生きるのがつらい

ほむ。F#でプログラミング言語「ほむほむ」して、さらに「ほむほむ」した

元ネタは、ゆろよろさんの
プログラミング言語「ほむほむ」 - ゆろよろ日記(id:yuroyoro)
http://d.hatena.ne.jp/yuroyoro/20110601/1306908421


F#でプログラミング言語「ほむほむ」

以前実装したF#でGrassなプログラムをちょっと改造しただけの手抜きだが、
F#でプログラミング言語「ほむほむ」実装した。
https://gist.github.com/1002805

open System

type token = Tw | TW | Tv | EoF
type Value = Value of char option * (Value -> Value)

let Write =
  let queue = new System.Collections.Generic.Queue<byte> ()
  (fun _ (c : char) ->
  let (|HEIGHT|_|) x = match x with | x when x >= 0x81uy && x <= 0x9Fuy || x >= 0xE0uy -> Some x | _ -> None
  let (|LOW|_|) x = match x with | x when x >= 0x40uy && x <= 0x7Euy || x >= 0x80uy && x <= 0xFCuy -> Some x | _ -> None
  match c |> byte with
   | HEIGHT x when queue.Count = 0 -> x |> queue.Enqueue 
   | LOW x when queue.Count <> 0 -> [|queue.Dequeue (); x|] |> System.Text.Encoding.GetEncoding(932).GetString |> Console.Write 
   | _ -> Console.Write c) ()

let Char x = Value (Some x, fun y ->
  let True = Value (None, fun x -> Value (None, fun _ -> x))
  let False = Value (None, fun _ -> Value (None, fun y -> y))
  match y with 
   | Value (Some y, _) -> match x,y with | x,y when x = y -> True | _ -> False
   | _ -> raise (new ArgumentException("Char : char以外はだめ")))

let InitStack = [
  Value (None, function
    | Value (Some c, _) as v -> Write c; v
    | _ -> raise (new ArgumentException("primitive Out : char以外はだめ")));  
  Value (None, function
    | Value (Some c, _) -> (int c + 1) % 256 |> char |> Char
    | _ -> raise (new ArgumentException("primitive Succ : char以外はだめ"))); 
  Char 'w';
  Value (None, fun x -> try stdin.ToString() |> char |> Char with eof -> x)]  

let Stack = (fun _ -> let stack = ref InitStack
                      fun x -> match x with 
                                | [] -> !stack 
                                | _  -> stack := x @ !stack; !stack) () 

open System.Text.RegularExpressions 
let rx = new Regex(@"\r\n|ほむ|(?<homu>[\s|\t]+[ほむ{1,}]+[\s$|\t$])")
let ReadFile filename = System.IO.File.ReadAllText(filename,System.Text.Encoding.GetEncoding("SHIFT-JIS")) 
let source = let source = @"c:\Code\ほむほむ\ほむほむ.txt" |>  ReadFile 
             seq { for s in rx.Matches(source) do 
                    let s = s.ToString()
                    if s = "ほむ" then
                      yield 'w'
                    elif Regex.IsMatch(s,"[\s|\t]+[ほむ{1,}]+[\s$|\t$]") then
                      for s in Regex.Matches(s,"ほむ") do
                        yield 'W' 
                    elif s = "\r\n" then 
                      yield 'v' } |> Seq.toArray 

let rec Analyze i = 
  let (|EOF|_|) x = match x with | x when x >= source.Length  -> Some x | _ -> None
  match i with 
   | EOF i -> i, EoF
   | _ -> match source.[i] with
           | 'w' -> i + 1, Tw
           | 'W' -> i + 1, TW
           | 'v' -> i + 1, Tv
           | _ -> Analyze (i + 1)

let rec Read target (index, token as position) i = 
  match token,target with 
    | token,target when token = target -> Read target (Analyze index) (i + 1)
    | _ -> position, i

let rec ReadBody position body = 
  let position, f = Read TW position 0 
  match f with 
   | 0 -> (position, List.rev body) 
   | _ -> let position, a = Read Tw position 0 
          ReadBody position ((f, a) :: body)

let rec App f a stack = 
  match stack with
   | [] -> raise (new ArgumentException("stack"))
   | v::st ->
    match a,f with
     | 1,_ -> let Value = List.nth stack (f - 1) 
              match Value with 
               | Value (c,func) -> func v
     | _,1 -> let arg = List.nth stack (a - 1) 
              let value  =  List.nth stack (f - 1)
              match value with 
               | Value (c,func) -> func arg
     | _   -> st |> App (f - 1) (a - 1)      

let Run = 
  let rec Run (index, token as position) = 
    match token with
      | EoF -> [] |> Stack |> App 1 1 |> ignore
      | Tw ->
        let position, argc = Read Tw position 0 
        let position, body = ReadBody position [] 
        let rec bind n stack arg = 
          let stack = arg :: stack
          match n with
           | 1 -> let rec loop stack body = 
                    match body with
                     | [] -> List.head stack
                     | (f, a) :: [] -> stack |> App f a
                     | (f, a) :: br -> loop ((stack |> App f a) :: stack) br
                  loop stack body
           | _ -> Value (None, bind (n - 1) stack)  
        [Value (None, bind argc (Stack[]))] |> Stack |> ignore
        Run position
      | TW ->
        let position, f = Read TW position 0 
        let position, a = Read Tw position 0
        [Stack [] |> App f a] |> Stack |> ignore
        Run position
      | Tv -> Run (Analyze index)

  let start = 
    let rec loop i = 
      let (index, token) as result = Analyze i 
      match token with
       | Tw | EoF -> result 
       | _ -> loop index
    loop 0
  start |> Run

[<STAThreadAttribute>]
do
  Run; stdin.ReadLine () |> ignore


以前、F#でちょっと草植えた記事はこちら
http://d.hatena.ne.jp/zecl/20100418/p1




オリジナルのほむほむのコードを書いてみる。

プログラミング言語「ほむほむ」で「ほむほむ」と出力してみる。


ほむほむ.txt

ほむ
ほむほむ ほむほむ ほむ ほむほむほむ ほむ
 ほむ ほむほむほむほむ ほむほむ ほむ ほむほむほむ ほむ ほむほむほむほむ ほむ ほむほむほむほむほむ ほむ ほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむ ほむ ほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむ


実行結果

ほむほむ


ほむほむ。実にほむほむですね。
ほむほむでほむほむできました。わーいやたーー\(^o^)/



追伸:オープンソースカンファレンス 2011 Hokkaidoに参加します。

のぶひささん(id:Nobuhisa)にお誘いいただきまして、OSC 2011 Hokkaidoに「F# User Group - Japan」のメンバーとして参加します。
セミナーを受講していないときは展示ブースにおりますのでので、ぜひお立ち寄りいただけたらと思います。
展示用のアプリ(F#+Silverlightな何か)を持参する予定です。よろしくお願いします。


もちろん、のぶひささんのセッションは見逃せません!!

これからの「言語」の話をしよう ―― 未来を生きるためのツール

http://d.hatena.ne.jp/Nobuhisa/20110528

F#でsleep sort

追記しました


元ネタは、以下あたり。


常識を覆すソートアルゴリズム!その名も"sleep sort"! - Islands in the byte stream
http://d.hatena.ne.jp/gfx/20110519/1305810786


Sleep sortの各言語での実装まとめ – Yuyak
http://www.yuyak.com/1339



ただいま流行っている sleep sort です。
既出かもしれませんが、せっかく書いたので置いておきます。

open System.Collections.Concurrent
let sleepSort f s =
  let q = new ConcurrentQueue<'a> ()
  seq {for x in s ->
         async { do! Async.Sleep((f:'a -> int) >> ( * ) 100 <| x)
                 q.Enqueue x }
      } |> Async.Parallel |> Async.RunSynchronously |> ignore
  q :> seq<'a>
             
["5";"3";"6";"3";"6";"3";"1";"4";"7"] |> sleepSort int |> Seq.iter (printf "%s,")
System.Console.ReadLine () |> ignore


いろんな書き方があると思うけど、一応F#らしさをプンプン匂わせてみたつもり。




追記:シグネチャ ファイルも書こう!


あ、シグネチャ ファイルって、実はあんまし書いたことなかったですよ^^;
ということで、せっかくなので書いておきます。


SleepSort.fsi

namespace Sort

[<AutoOpen>]
module Sort = 
  val sleepSort : ('a -> int) -> seq<'a> ->  seq<'a>

SleepSort.fs

namespace Sort

[<AutoOpen>]
module Sort = 
  open System.Collections.Concurrent
  let sleepSort f s =
    let q = new ConcurrentQueue<'a> ()
    seq {for x in s ->
            async { do! Async.Sleep((f:'a -> int) >> ( * ) 100 <| x)
                    q.Enqueue x }
        } |> Async.Parallel |> Async.RunSynchronously |> ignore
    q :> seq<'a>
             
  ["5";"3";"6";"3";"6";"3";"1";"4";"7"] |> sleepSort int |> Seq.iter (printf "%s,")
  System.Console.ReadLine () |> ignore

これでよいかな?



追記:よいシグネチャ ファイルは、よいドキュメントでもある


ぜんぜん勝ててなかったorz... なるほど。勉強になります。
ホッピーうまいですよね!ということで、コメントと引数名をさらに追記しました。


SleepSort.fsi

namespace Sort

[<AutoOpen>]
module Sort = 

  /// 定義であり、ドキュメントにもなる。ここで、関数に対する熱き思いをぶつけるといい。
  /// 常識を覆すソートアルゴリズム!その名も"sleep sort"!のシグネチャです。
  val sleepSort : projection:('T -> int) -> source:seq<'T> -> seq<'T>

SleepSort.fs

namespace Sort

[<AutoOpen>]
module Sort = 
  open System.Collections.Concurrent

  /// 常識を覆すソートアルゴリズム!その名も"sleep sort"!をしちゃいます。
  let sleepSort f s =
    let q = new ConcurrentQueue<'a> ()
    seq {for x in s ->
            async { do! Async.Sleep(f >> ( * ) 100 <| x)
                    q.Enqueue x }
        } |> Async.Parallel |> Async.RunSynchronously |> ignore
    q :> seq<'a>
             
  ["5";"3";"6";"3";"6";"3";"1";"4";"7"] |> sleepSort int |> Seq.iter (printf "%s,")
  System.Console.ReadLine () |> ignore


という具合に、引数名を変えてもOKだし、ジェネリックの型名を'Tから'aに変えても問題ない、と。
そして、f の型についてはシグネチャファイルに定義してあるので省略可能になる、と。
こんどこそ、これで勝つる!


追記:シグネチャ ファイルを自動生成しよう


シグネチャファイル(.fsi)をよしなに自動生成したあとに、自分でいろいろ追記すればよい、と。
でも、大幅にコードの構成を変えた場合はやり直し。それはちょっとツライね。

F#では、パターンマッチを「match x with」と書く流派と「x |> function」と書く流派がございます。

とあるF#er達のつぶやき


ながと(@nagat01)さんというか、ながと2(@nagat02)さんが、私のツイートに反応してくれていました。うれしいな。
「いにしえからの言い伝えによると」というしょうもない冗談というか、余計意外の何ものでもない表現を付け加えてしまったためか、
私の言いたかったことは伝わっていないようです。なので、たいした話ではないのですがちょっと書いてみます。ながとさんのアイコンがかわいいです。



「x |> function」という書き方だと、インデントを浅く保てる!

さて、まずコンピューテーション式を用いて、最近マイブームのMaybeモナドを用意してみました。
もちろんモナド則を満たしています。だらだら説明する元気もないので、百聞は一見になんとやらに則って、
詳細についてはコードおよびコメントを参照してください。(モナドうんぬんはわからなくても問題ありません)

open System
let bind x k =
    match x with
    | Some value -> k value
    | None   -> None

let mreturn x = Some x

/// Maybeモナド
type MaybeBuilder() =
  member this.Bind(x, k) = bind x k
  member this.Return(x)  = mreturn x
  member this.ReturnFrom(x) = x
  member this.Delay(f)   = f()
  member this.Combine(a, b) = if Option.isSome a then a else b ()
  member this.Zero() = None
  member this.For(inp,f) =
      seq {for a in inp -> f a}
  member this.Yield(x) = mreturn x
  member this.YieldFrom(x) = x

let maybe = new MaybeBuilder()

(*
let x = 1
let m = maybe { return 3 }
let f x = maybe { return 4 + x }
let g x = maybe { return 2 * x }
let (>>=) m f = maybe {let! x = m
                       return! f x}

let return' x = maybe { return x }

let prove (left,right) = 
  maybe {let! x = left
         let! y = right
         return x = y}

prove (return' x >>= f, f x) |> Option.get |> fun b -> printfn "モナド則1 : %b" b |> ignore // true
prove (m >>= return', m) |> Option.get |>fun b -> printfn "モナド則2 : %b" b |> ignore // true
prove ((m >>= f) >>= g, m >>= (fun x -> f x >>= g)) |> Option.get |> fun b -> printfn "モナド則3 : %b" b |> ignore // true
Console.ReadLine () |> ignore
*)

type QB = System.Linq.IQueryable 
let charlotte () = invalidArg "mo:Magica option" "マミられました"

type Magica =
  |Madoka of QB
  |Sayaka of QB
  |Mami of QB
  |HomuHomu of QB
  |Kyouko of QB

(* 一般的な書き方(1)
   "match x with"と書く流派の典型 
   改行してから"match x with"でパターンマッチします *)
let keiyaku0 mo = 
  maybe { let! x = mo
          return
            match x with
            |Madoka   m -> m.Expression 
            |Sayaka   m -> m.Expression
            |Mami     m -> charlotte ()
            |HomuHomu m -> m.Expression 
            |Kyouko   m -> m.Expression } 

(* return と同じ行に"match x with"を書きます。できればこう書いてしまいたいんだけど、
   インデントがおかしいよ!と怒られてしまいます。*)
let keiyaku1 mo = 
  maybe { let! x = mo
          return match x with
          |Madoka   m -> m.Expression 
          |Sayaka   m -> m.Expression
          |Mami     m -> charlotte ()
          |HomuHomu m -> m.Expression 
          |Kyouko   m -> m.Expression } 

(* 一般的な書き方(2)
   "match x with"と書く流派の別解
   return と同じ行に"match x with"を書きます。でも、インデントが深くなってしまうま *)
let keiyaku2 mo = 
  maybe { let! x = mo
          return match x with
                 |Madoka   m -> m.Expression 
                 |Sayaka   m -> m.Expression
                 |Mami     m -> charlotte ()
                 |HomuHomu m -> m.Expression 
                 |Kyouko   m -> m.Expression } 

(* "x |> function" と書く流派なら、すっきり!
   もちろん、この書き方しかしないというわけではないけど、
   インデントを浅く保つことができるので、わたしはこの書き方が好きです
   モテるF#女子はこう書きます>< *)
let keiyaku3 mo = 
  maybe { let! x = mo
          return x |> function
          |Madoka   m -> m.Expression 
          |Sayaka   m -> m.Expression
          |Mami     m -> charlotte ()
          |HomuHomu m -> m.Expression 
          |Kyouko   m -> m.Expression } 


「x |> function」の方をより好むと言っているだけで、
「match x with」とは書かないと言っているわけではありませんので、くれぐれも誤解のないように。

判別共用体のケース識別子は直接公開せず、インターフェイスをアクティブパターンで提供するのがスマートっぽいよ

とあるF#er達のつぶやき


わたしのつぶやきが不正確かつ言葉足らずすぎて上手く伝わらなかったのが事の発端です ><。
あまり考えずにつぶやくと、いらぬ誤解を招いたりスルーされたりするから、気を付けよう(まぁ、所詮つぶやきですが)。
イケメンF# MVPの@bleisさんにリツイートされたときは、「えっ」て思っちゃいましたが、たぶん言わんとすることが伝わったということでしょう。流石です!
ということで、最後はのぶひさ先生(id:Nobuhisa)に颯爽と登場していただいたのですが、
執筆中の記事が読めるのはまだ先になりそうということで、わたしが言いたかった事をちょっと説明してみます。


ライブラリ側としては、クライアントに判別共用体のケース識別子を公開したいことってあんまりないよね。

判別共用体のケース識別子の増減または付随する型の変更について、クライアントコードに対する影響範囲を少なくすることを考えています。
ライブラリの判別共用体のケース識別子は、privateまたはinternalで宣言することを検討します。要するにアクセシビリティを意識しましょうということでした。
つまりこれは、ライブラリで完結されるべきことはライブラリ内に隠ぺいしておくのが望ましいとも言っています。
これは、F# MVPいげ太さんのつぶやきの中でも登場した「Draft F# Component Design Guidelines(August 2010) (PDF)」にて推奨されている内容そのものです。



「Draft F# Component Design Guidelines(August 2010)」に出てくるサンプルコードを参考にして解説してみます。
まずは、ライブラリ側にケース識別子を隠ぺいしない判別共用体を宣言してみます。また、評価用の関数evaluateを定義します。

module Module1

type PropLogic =  
  | And of PropLogic * PropLogic  
  | Not of PropLogic 
  | True 

let rec evaluate = function
  | And(x,y) -> (evaluate x) && (evaluate y) 
  | Not x -> not (evaluate x)  
  | True -> true 


クライアント側から利用します。

open System
open Module1

let hoge = And(True,Not(True))
hoge|> evaluate |> printfn "%b" 

type Test = 
  | P of PropLogic 
  | B of bool

let (|Test|) = function
  | P a -> evaluate a
  | B a -> a

let homu = function
  | Test x -> x

homu (Test.P hoge) |> printfn "%b" 
homu (Test.B true) |> printfn "%b" 
Console.ReadLine () |> ignore


実行結果

false
false
true


上記は一見問題なさそうです。しかしこれはクライアント側がPropLogic判別共用体のケース識別子を直接参照していないからにすぎません。
また、ライブラリの判別共用体のケース識別の増減や型の変更などにとても脆いです。
判別共用体からケース識別子が隠ぺいされていないので、クライアント側がとても自由になります。次の実装を見てください。


open System
open Module1

let hoge = And(True,Not(True))
hoge |> evaluate |> printfn "%b" 

type Test = 
  | P of PropLogic   
  | B of bool

let (|Test|) = function
  | P a -> match a with
           | And(x,y) -> (evaluate x) || (evaluate y) 
           | Not x -> not (evaluate x)  
           | True -> false 
  | B a -> a

let homu = function
  | Test x -> x

homu (Test.P hoge) |> printfn "%b" 
homu (Test.B true) |> printfn "%b" 
Console.ReadLine () |> ignore


実行結果

false
true
true


これはひどい。「クライアントが自由に実装できて拡張性が高くてよい」と思いますか?それは大きな間違いです。
面白いことに、これは「自由でありながら不自由である」ということを意味しています。不必要で不適切な拡張はバグの温床となります。
ライブラリ内で完結されるべきことはライブラリ内に隠ぺいしておくのが望ましいということです。



変更に脆いことを確認するために、ライブラリのPropLogic判別共用体のケース識別子に「False」を増やしてみましょう。

type PropLogic =  
  | And of PropLogic * PropLogic  
  | Not of PropLogic 
  | True 
  | False

let rec evaluate = function
  | And(x,y) -> (evaluate x) && (evaluate y) 
  | Not x -> not (evaluate x)  
  | True -> true 
  | False -> false


クライアント側にの実装

open System
open Module1

let hoge = And(True,Not(True))
hoge |> evaluate |> printfn "%b" 

type Test = 
  | P of PropLogic   
  | B of bool

let (|Test|) = function
  | P a -> match a with
           | And(x,y) -> (evaluate x) || (evaluate y) 
           | Not x -> not (evaluate x)  
           | True -> false 
  | B a -> a

let homu = function
  | Test x -> x

homu (Test.P hoge) |> printfn "%b" 
homu (Test.P False) |> printfn "%b" 
homu (Test.B true) |> printfn "%b" 
Console.ReadLine () |> ignore


実行結果

false
true
'Microsoft.FSharp.Core.MatchFailureException' のハンドルされていない例外が ConsoleApplication1.exe で発生しました。


「homu (Test.P False) |> printfn "%b" 」を追加しました。
クライアント側で自由に変更した部分のパターンマッチで「Falseケース識別子」についての判定がないために例外が発生します。
これはコンパイル時に怒ってもらえません*1。非常に残念です。
ライブラリの判別共用体のケース識別子を削除した場合は、クライアント側で削除されたケース識別子を参照している場所すべてに影響がでてしまいます。
クライアント側が必要以上にフリーダムすぎるとカオスが訪れるということです。被害が拡大すると目も当てられません。
クライアントコードに対する影響範囲を少なくすることを考えて、判別共用体のケース識別子は private または internal で宣言することを検討しましょう。
ライブラリで完結されるべきことはライブラリ内に隠ぺいしておきましょう。


判別共用体のケース識別子を直接公開せず、アクティブパターンでインターフェイスを提供するのがスマート


ということで、ライブラリにケース識別子を隠ぺいした判別共用体を宣言します。
また、Evaluateのためのインターフェイスとしてアクティブパターンを定義します。

module Module1

type PropLogic =  
  private 
  | And of PropLogic * PropLogic  
  | Not of PropLogic 
  | True 

let (|Evaluate|) = 
  let rec evaluate = function
    | And(x,y) -> (evaluate x) && (evaluate y) 
    | Not x -> not (evaluate x)  
    | True -> true 
  evaluate

let makeAnd x y= And(x,y)
let makeNot x = Not(x) 
let makeTrue = True 

クライアント側

open System
open Module1

let hoge = makeAnd makeTrue (makeNot(makeTrue))
hoge |> (|Evaluate|) |> printfn "%b" 

type Test = 
  | P of PropLogic 
  | B of bool

let (|Test|) = function
  | P a -> match a with | Evaluate b -> b
  | B a -> a

let homu = function
  | Test x -> x

homu (Test.P hoge) |> printfn "%b" 
homu (Test.B true) |> printfn "%b" 
Console.ReadLine () |> ignore


実行結果

false
false
true


ケース識別子をprivate(あるいはinternal)で宣言することで、ライブラリを参照しているクライアント側からは
And、Not、Trueの3つのケース識別子について参照することができなくなります。ですから、クライアントで自由がききません。
判別共用体に対するEvaluateは、ライブラリから公開されているアクティブパターン「(|Evaluate|)」という1つのインターフェイスのみで完結します。




いげ太さんのつぶやきで気が付いたのですが、「Draft F# Component Design Guidelines(August 2010)」に例がでていました。
真似をして、マルチパラダイムを意識して書き直してみましょう。

type PropLogic =  
  private 
  | And of PropLogic * PropLogic  
  | Not of PropLogic 
  | True 
  /// 主にC#やVB.NETから呼ばれる事を意識
  member x.Evaluate =  
       match x with  
       | And(x,y) -> x.Evaluate && y.Evaluate 
       | Not x -> not x.Evaluate  
       | True -> true 
  /// 主にC#やVB.NETから呼ばれることを意識 
  static member MakeAnd(x,y) = And(x,y)
  static member MakeNot(x) = Not(x)
  static member MakeTrue = True

let (|Evaluate|) x = (x:PropLogic).Evaluate 
let makeAnd = PropLogic.MakeAnd 
let makeNot = PropLogic.MakeNot 
let makeTrue = PropLogic.MakeTrue 


クライアント側

open System
open Module1

let hoge = PropLogic.MakeAnd(makeTrue,makeNot(makeTrue))
hoge.Evaluate |> printfn "%b" 

type Test = 
  | P of PropLogic 
  | B of bool

let (|Test|) = function
  | P a -> match a with | Evaluate b -> b
  | B a -> a

let homu = function
  | Test x -> x

homu (Test.P hoge) |> printfn "%b" 
homu (Test.B true) |> printfn "%b" 
Console.ReadLine () |> ignore


実行結果

false
false
true


最後にケース識別子を隠ぺいした判別共用体にも「Falseケース識別子」を追加して変更に強いことを確認してみましょう。

module Module1

type PropLogic =  
  private 
  | And of PropLogic * PropLogic  
  | Not of PropLogic 
  | True 
  | False 
  /// 主にC#やVB.NETから呼ばれることを意識 
  member x.Evaluate =  
       match x with  
       | And(x,y) -> x.Evaluate && y.Evaluate 
       | Not x -> not x.Evaluate  
       | True -> true 
       | False -> false 
  /// 主にC#やVB.NETから呼ばれることを意識 
  static member MakeAnd(x,y) = And(x,y)
  static member MakeNot(x) = Not(x)
  static member MakeTrue = True
  static member MakeFalse = False

let (|Evaluate|) x = (x:PropLogic).Evaluate 
let makeAnd = PropLogic.MakeAnd 
let makeNot = PropLogic.MakeNot 
let makeTrue = PropLogic.MakeTrue 
let makeFalse = PropLogic.MakeFalse 


クライアント側

open System
open Module1

let hoge = PropLogic.MakeAnd(makeTrue,makeNot(makeTrue))
hoge.Evaluate |> printfn "%b" 

type Test = 
  | P of PropLogic 
  | B of bool

let (|Test|) = function
  | P a -> match a with | Evaluate b -> b
  | B a -> a

let homu = function
  | Test x -> x

homu (Test.P hoge) |> printfn "%b"
homu (Test.P makeFalse) |> printfn "%b" 
homu (Test.B true) |> printfn "%b" 
Console.ReadLine () |> ignore


実行結果

false
false
false
true


OKですね。クライアントに与える影響を最小限におさえてライブラリを変更することができました。
このようにケース識別子を追加する場合はもちろん、ケースの削除あるいは型を変更するような場合にも影響範囲を最小限に抑えることができます。


まとめ

ライブラリで判別共用体を宣言する場合は、ケース識別子をクライアントに対して隠ぺいすることを検討しましょう。
クライアントに与える影響を最小限におさえてバグの混入を防ぎ、変更に強いライブラリを作ることができます。
またその場合、判別共用体に対するインターフェイスをアクティブパターン*2で提供するのがスマートです(必要に応じて、ふつうの関数も提供するのも良い)。
その場合、パーシャルアクティブパターンの利用も検討してみるとよいでしょう。F#で良いコードを書くために、ぜひとも押さえておきたいところです。

*1:コンパイラによる警告は出ます

*2:バナナクリップ