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

モナディックなパーサ・コンビネータFParsecを使おう。てゆうかParsec(Haskell)のApplicativeスタイルがやばい。

Parsec(Haskell)のApplicativeスタイルがやばすぎるので、FParsecでApplicativeスタイルしてみた。


FParsecとは

FParsec とは Haskell のパーサ・コンビネータライブラリ Parsec をF#に移植したものです*1
では、パーサ・コンビネータとはなんでしょうか。簡単に言うとパーサ(構文解析)の関数を組み合わせることで、
新たに複雑なパーサを作り上げるための仕組み(フレームワーク)と考えておけばよいでしょう。ファイルやいろいろな型のデータの構文解析に力を発揮します。


構文解析といえば正規表現を思い浮かべる人も多いかもしれません。正規表現は多くの場合とても便利ですが、複雑なデータ構造を扱うには不向きです。
パーサ・コンビネータは、時に正規表現に対する“より良い代替案”になり得ます*2
FParsecを用いることで、無限先読みの文脈依存文法を解析したり、BNF記法で表すような複雑なパーサも比較的簡単に作ることができます。
噂によるとHaskellのParsecはLL構文解析において最高の性能を発揮するそうです。FParsecも同じような雰囲気ですたぶん。
ということで、難しいことはよくわかりませんが、魔神英雄伝ワタルよろしくなんだか面白カッコ良さそうなので利用しない手はないです。


環境

F# 2.0.0
FParsec 0.8.0.0


準備1:FParsecをビルドする

FParsecを利用するには、まずここ から FParsec のソースコードをダウンロードし、適当なディレクトリに解凍してビルドします。
すると、めでたくFParsec.dllとFParsecCS.dllを手に入れることができます。ありがたや。


準備2:参照設定とモジュールのオープン

さっそくFParsec.dllとFParsecCS.dllを参照設定に追加します。




そして、利用するFParsecの各モジュールをオープンします。

open System
open FParsec.Primitives
open FParsec.CharParsers
open FParsec.Error


これでFParsecを利用する環境が手に入りました。やったね。


FParsec:パースの基本

FParsec - A Parser Combinator Library for F#に解説と基本的なサンプルコードが出ています。
サンプルコードを動かすと、なんとなく雰囲気がつかめます。
ただ、ドキュメントの内容が古くなってしまっていて、すべてをそのまま適用できないのが残念なところです。


さっそく何かパースしてみましょう。
まったく面白くもなんともありませんが、例えば1つの文字をパースするパーサはこう書けます。

let test s = match run letter s with
             | Success (r,us,p)   -> printfn "success: %A" r
             | Failure (msg,err,us) -> printfn "failed: %s" msg

let _ = test "ふじこlp777"
let _ = test "1ふじこlp777"


実行結果

success: 'ふ'
failed: Error in Ln: 1 Col: 1
1ふじこlp777
^
Expecting: letter


letterの型はParserとなっています。
正確には、unicode letter(System.Char.IsLetterがtrueを返すもの)をパースするシンプルなパーサです。
runはパーサと対象を受け取ってパースを実行して結果を返す関数で、
成功した場合はSuccess、失敗した場合はFailureを判別共用体 ParserResult<'Result,'UserState>で返します*3
例の「"1ふじこlp777"」のパースは、先頭が数字(System.Char.IsLetter('1')はfalse)なので失敗していることがわかります。



もちろん letter の他にも以下のような基本的なパーサがいくつも定義されています。
でもぶっちゃけ全然足りてないですね。字句解析を真面目にやるなら不足分は自分で作るしかないですね。

anyOf: string -> Parser<char,'u>
 「任意の文字列(引数:string)に含まれるすべてのchar」をパース

noneOf: string -> Parser<char,'u>
 「任意の文字列(引数:string)に含まれるすべてのchar」を含まないパース

asciiUpper: Parser<char,'u>
 ASCII letter (大文字)をパース

asciiLower: Parser<char,'u>
 ASCII letter (小文字)をパース

asciiLetter: Parser<char,'u>
 ASCII letterをパース

upper: Parser<char,'u>
 unicode letter(大文字)をパース

lower: Parser<char,'u>
 unicode letter(小文字)をパース

digit: Parser<char,'u>
 数字([0-9])をパース

ちなみに1つ以上の複数のunicode letter(System.Char.IsLetterがtrue)をパースするパーサはこう書けます。

let test2 s = match many1 letter |> run <| s with
              | Success (r, s, p) -> printfn "%A" r
              | Failure (msg, err, s) -> printfn "%s" msg

let _ = test2 "ふじこlp777"
let _ = test2 "1ふじこlp777"


実行結果

['ふ'; 'じ'; 'こ'; 'l'; 'p']
Error in Ln: 1 Col: 1
1ふじこlp777
^
Expecting: letter


選択

(<|>)、choice関数

(<|>)演算子あるいは、choice関数でパーサを選択することができる。
意味としては、正規表現で言うところの「|」(どれかにマッチ)と同じと考えて差し支えはない。
ただし、(<|>)演算子は、常に左側の選択肢を最初に試すということには注意。

unicode letterあるいは数字に一致する1文字以上にパース

let ld = (letter <|> digit |> many1Chars) 
         |> run <| "ABC12D34;EF5"
match ld with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


実行結果

"ABC12D34"


もちろんコンピューテーション式で記述することもできる

let ld' = parse {let! anc = choice [letter;digit] |> many1Chars
                 return anc}
          |> run <| "ABC12D34;EF5"
match ld' with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg
Console.WriteLine () |> ignore


(.>>)、(>>.)

括弧内の1つ以上の数字をcharのリストとしてパース
many1がみそですね。ただのmanyだと1文字もマッチしなくてもパースが成功します。

let e1 = run (pchar '(' >>. (many1 digit) .>> pchar ')') "(123456)"
match e1 with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


実行結果

['1'; '2'; '3'; '4'; '5'; '6']


括弧内の1つ以上の数字をstringとしてパース
many1Charsがみそですね。ただのmanyCharsもmanyと同様に、1文字もマッチしなくてもパースが成功します。

let f = pchar '(' >>. (many1Chars digit) .>> pchar ')'
        |> run <| "(123456)"
match f with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


実行結果

"123456"


unicode letterおよび#+.をパース(ただし、lowerは読み捨てる)

let notlower1 = (letter <|> anyOf "#+.") .>> manyChars lower |> manyChars
               |> run <| "F#C#C++javaVB.NET"
match notlower1 with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


実行結果

"F#C#C++VB.NET"

連結

連結とはまさにパースとパースを繋ぎ合わせることです。
以下は、「unicode letterと数字が隣り合う文字列」に一致する1文字以上にパース

let cn = (pipe2 letter (many1Chars digit) (fun x y -> string(x)+string(y)) |> manyStrings) 
         |> run <| "A1B2C345;D6"
match cn with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


もちろんコンピューテーション式として記述することもできる

let cn' = parse {let! anc = letter 
                 let! d = many1Chars digit 
                 return string(anc)+string(d)} |> manyStrings
          |> run <| "A1B2C345;D6"
match cn' with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg

unicode letterと隣り合うセミコロンも許容する

let cn'' = parse {let! anc = letter 
                  let! d = many1Chars digit <|> (anyOf ";" |> many1Chars)
                  return string(anc)+string(d)} |> manyStrings
           |> run <| "A1B2C3D;E45;F6"
match cn'' with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


実行結果

"A1B2C3D;E45"


正規表現の併用


正規表現を使わない場合

let str = @"(*comme
nt123a*)bc4d*)"

let comment1 = pstring "(*" >>. many1Chars (choice [digit;letter;newline] ) .>> pstring "*)"
               |> run <| str
match comment1 with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


正規表現を併用して使う

let comment2 = between (pstring "(*") (pstring "*)") (regex "[^*)]+") 
               |> run <| str
match comment2 with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg

実行結果

"comme
nt123a"
"comme
nt123a"

FParsecは正規表現の代替案になり得るのは間違いない。
しかし、正規表現の長所すべてを否定するものではない。
正規表現の長所はそのままFParsecの中で生かすことができる。

let kanji = regex "[一-龠]" <?> "kanji"

let kr = kanji |> many1 |> run <| "読書百遍意自ずから通ず"
match kr with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg

実行結果

["読"; "書"; "百"; "遍"; "意"; "自"]


というように、正規表現を導入することで漢字のパーサを簡単かつ正確に作ることができます。
(<?>)演算子は、解析器が失敗したときのエラーメッセージをカスタマイズするためのものです。



とあるパーサをn回適用したパーサ


とあるパーサをn回適用したパーサを取得する関数repeatを定義する

let repeat n p =
  let rec repeat n p result =
    parse {if n > 0 then
             let! x = p
             let! xs = repeat (n - 1) p (result@[x])
             return xs
           else
             return result}
  repeat n p []


unicode letterを読み捨てて、数字3桁をパースする

let d3 = letter <|> digit .>> many letter >>. repeat 3 digit
         |> run <| "aBc1234dEf"

match d3 with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


実行結果

['1'; '2'; '3']
let w = repeat 3 (pstring "うぇ")
         |> run <| "うぇうぇうぇうぇうぇうえぇえぇええwwww"

match w with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


実行結果

["うぇ"; "うぇ"; "うぇ"]

先読み

FParsecにはattemptがあって、これを使って先読みを表現することができる(Parsecでいうところのtryに相当)。
attemptは解析関数(Parser<_,_>)を1つ取って、解析が成功しなかった場合、attempは入力を消費しない。
なので、(<|>)演算子の左側でattempを使うと、attemp内での消費がなかったものとして右側を試しま。attemptって名前のとおりです。

let w2 = repeat 3 (pstring "うぇうぇ")
         |> run <| "うぇうぇうぇうぇうぇうえぇえぇええwww"

match w2 with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


let w3 = attempt (repeat 3 (pstring "うぇうぇ")) <|> (repeat 5 (pstring "うぇ"))
         |> run <| "うぇうぇうぇうぇうぇうえぇえぇええwww"

match w3 with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


実行結果

Error in Ln: 1 Col: 9
うぇうぇうぇうぇうぇうえぇえぇええwww
        ^
Expecting: 'うぇうぇ'

["うぇ"; "うぇ"; "うぇ"; "うぇ"; "うぇ"]


単純なパーサ:郵便番号

郵便番号パーサ 正規表現

let r = parse {let! d = regex "^\d{3}-\d{4}$" in return d}
let zip = (r, "001-0016") ||> run
match zip with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


郵便番号パーサ 正規表現未使用

let zip2 = parse {let! p= pipe3 (repeat 3 digit) (pchar '-') (repeat 4 digit) (fun x y z -> x@[y]@z)
                  do! notFollowedBy (digit <|> letter)
                  return new string (List.toArray p)}
           |> run <| "001-0016"
match zip2 with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


実行結果

"001-0016"
"001-0016"


単純なパーサ:改行区切り

改行区切りでパースする

let pline =
   parse {let! first = anyChar 
          if first = '\n' then 
            return "" 
          else 
            let! txt = restOfLine 
            return (first.ToString()+txt)} 

let strings' = run (many pline) "\n\nHoge1\nFuga\n\nPiyo" 
match strings' with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg

内部的にどうなっているのか、もっと詳細な書き方をした場合

let pline': Parser<string, unit> =
    fun state ->
       let mutable str = null
       let newState = state.SkipRestOfLine(true, &str)
       if not (LanguagePrimitives.PhysicalEquality state newState) then
           Reply(str, newState)
       else
           Reply(Error, NoErrorMessages, newState)

let strings'' = run (many pline') "\n\nHoge1\nFuga\n\nPiyo"
match strings'' with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg


実行結果

[""; ""; "Hoge1"; "Fuga"; ""; "Piyo"]
[""; ""; "Hoge1"; "Fuga"; ""; "Piyo"]


Applicativeスタイルがやばい

Applicativeのススメ - あどけない話
http://d.hatena.ne.jp/kazu-yamamoto/20101211/1292021817

Real World Haskell - Chapter 16. Using Parsec Applicative functors for parsing
http://book.realworldhaskell.org/read/using-parsec.html



Applicativeスタイル*4がやばい。やばすぎるよ!
ということで、冒頭でも書いたが、FParsecでApplicativeスタイルしてみた。


module FParsec.Applicative
open System
open FParsec.Primitives
open Microsoft.FSharp.Core.Operators.Unchecked 

/// ap :: Monad m => m (a -> b) -> m a -> m b
let inline ap f a = f >>= fun f' -> a >>= fun a' -> preturn (f' a') 

/// (<*>) :: Applicative f => f (a -> b) -> f a -> f b
let inline (<*>) f a = ap f a

/// (<**>) :: Applicative f => f a -> f (a -> b) -> f b
let inline apr f a = a <*> f
let inline (<**>) f a = apr f a

/// liftA :: Applicative f => (a -> b) -> f a -> f b
let inline liftA f a = a |>> f 

/// (<$>) :: Functor f => (a -> b) -> f a -> f b
let inline (<!>) f a = liftA f a 

/// (<$) :: Functor f => a -> f b -> f a
let inline (<!) f a = preturn f .>> a
let inline (!>) f a = f >>. preturn a

/// liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
let inline liftA2 f a b = pipe2 a b f         // preturn f <*> a <*> b 
let inline (<!!>) f a b = liftA2 f a b 

/// liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d
let inline liftA3 f a b c = pipe3 a b c f
let inline (<!!!>) f a b c = liftA3 f a b c

/// ( *>) :: Applicative f => f a -> f b -> f b
let inline ( *>) x y = x >>. y                 // liftA2 (fun _ z -> z) x y 

/// (<*) :: Applicative f => f a -> f b -> f a
let inline ( <*) x y = x .>> y                 // liftA2 (fun z _ -> z) x y 

/// sequenceA :: Applicative f => [f a] -> f [a]
let sequenceA ps = List.foldBack (liftA2 (fun x y -> x::y)) ps (preturn [])

/// sequenceA_ :: Applicative f => [f a] -> f ()
let sequenceA_ ps = List.fold ( *>) (preturn ()) ps

/// mapA :: Applicative f => (a -> f b) -> [a] -> f [b]
let mapA f xs = sequenceA (List.map f xs)

/// mapA_ :: Applicative f => (a -> f b) -> [a] -> f ()
let mapA_ f xs = sequenceA_ (List.map f xs)

/// foreverA :: Applicative f => f a -> f b
let rec foreverA a = a *> foreverA a

/// asum :: Alternative f => [f a] -> f a
let sumA ps = List.fold (<|>) (preturn defaultof<'a>) ps

//filterA :: Applicative f => (a -> f Bool) -> f [a] -> f [a]
let filterA f ps = 
  let addIf x b xs = if b then x::xs else xs
  let consA x a = liftA2 (addIf x) (f x) a
  List.foldBack consA ([]) ps

/// zipWithA :: Applicative f => (a -> b -> f c) -> [a] -> [b] -> f [c]
let map2A f xs ys = sequenceA (List.map2 f xs ys)

/// zipWithA_ :: Applicative f => (a -> b -> f c) -> [a] -> [b] -> f ()
let map2A_ f xs ys = sequenceA_ (List.map2 f xs ys)

/// mapAndUnzipA :: Applicative f => (a -> f (b, c)) -> [a] -> f ([b], [c])
let mapAndUnzipA f xs = liftA List.unzip (mapA f xs)

/// replicateA :: Applicative f => Int -> f a -> f [a]
let replicateA n a = sequenceA (List.replicate n a)

/// replicateA_ :: Applicative f => Int -> f a -> f ()
let replicateA_ n a = sequenceA_ (List.replicate n a)

/// unlessA :: Applicative f => Bool -> f () -> f ()
let unlessA b a = if b then preturn () else a

/// guardA :: Alternative f => Bool -> f ()
let guardA b = unlessA b (preturn ())

/// whenA :: Applicative f => Bool -> f () -> f ()
let whenA b a = if b then a else preturn ()


ということで、FParsecはHaskellのParsec同様にモナディックなパーサ・コンビネータであることがわかります。
ちなみに、コメントの型表記はHaskell形式です。
当然ながらF#的な型を表すものではありませんし正確でもありません。あしからず。



Applicativeスタイルを適用すると、

let foo m1 m2 f = 
  parse {let! a= m1
         let! b = m2
         return f a b}

というように、コンピューテーション式でこう書いていたものが

let foo' m1 m2 f = f <!> m1 <*> m2

こう書けます。あらまあ、きれいにコンピューテーション式なスタイルがなくなりました。


(<!>)演算子と(<*>)演算子を隠してみると以下のようになります。

let foo' m1 m2 f = f m1 m2

これはつまり、Applicativeがf m1 m2 という関数適用の形になっていることを表します。
Applicaitveを用いると、パーサをとても自然に表現することができるということです。
これが Applicative (適用できる)と呼ばれる由来です。


CSVファイルのパース


リア充本としても知られる「Real World Haskell」に掲載されているParsecによるCSVファイルパーサのお手本。
http://book.realworldhaskell.org/read/using-parsec.html

import Text.ParserCombinators.Parsec

csvFile = endBy line eol
line = sepBy cell (char ',')
cell = quotedCell <|> many (noneOf ",\n\r")

quotedCell = 
    do char '"'
       content <- many quotedChar
       char '"' <?> "quote at end of cell"
       return content

quotedChar =
        noneOf "\""
    <|> try (string "\"\"" >> return '"')

eol =   try (string "\n\r")
    <|> try (string "\r\n")
    <|> string "\n"
    <|> string "\r"
    <?> "end of line"

parseCSV :: String -> Either ParseError [[String]]
parseCSV input = parse csvFile "(unknown)" input

main =
    do c <- getContents
       case parse csvFile "(stdin)" c of
            Left e -> do putStrLn "Error parsing input:"
                         print e
            Right r -> mapM_ print r

実にエレガントですね。



何も考えずに、F#へ移植してみる

let quotedChar = noneOf "\"" 
             <|> attempt (pstring "\"\"" >>. pchar '"')

let quotedCell = pchar '"' 
             >>. manyChars quotedChar 
             .>> pchar '"' 
             <?> "quote at end of cell"

let cell = quotedCell
       <|> manyChars (noneOf ",\n\r")

let line = sepBy cell (pchar ',')

let eol = attempt (newline) <?> "end of line"
let csvFile = sepEndBy line eol
let parseCSV input = (csvFile, input) ||> run

let ReadFile filename = System.IO.File.ReadAllText(filename,System.Text.Encoding.GetEncoding("UTF-8")) 
let csv = @"D:\test\Data\sample.csv" |>  ReadFile
match parseCSV csv with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg
Console.ReadLine () |> ignore

なんという写経。




適用箇所少ないけど、Applicativeスタイルにしてみる。

open FParsec.Applicative

let quotedChar = noneOf "\"" <|> attempt ('"' <! pstring "\"\"")
let quotedCell = pchar '"' *> manyChars quotedChar <* pchar '"' 
             <?> "quote at end of cell"
let cell = quotedCell <|> manyChars (noneOf ",\n\r")
let line = sepBy cell (pchar ',')
let eol = attempt newline <?> "end of line"
let csvFile = sepEndBy line eol
let parseCSV input = (csvFile, input) ||> run

let ReadFile filename = System.IO.File.ReadAllText(filename,System.Text.Encoding.GetEncoding("UTF-8")) 
let csv = @"D:\test\Data\sample.csv" |>  ReadFile 

match parseCSV csv with
| Success (r, s, p) -> printfn "%A" r
| Failure (msg, err, s) -> printfn "%s" msg
Console.ReadLine () |> ignore


ふつくしい…!



あわせて読みたい

マジあわせて読みたい


シンプルで高速な構文解析ライブラリ「Parsec」を.NETで使う^^ - yuji1982の日記
http://d.hatena.ne.jp/yuji1982/20080627/1214558307

最強のパーザー、Parser Combinator - 純粋関数型雑記帳
http://d.hatena.ne.jp/tanakh/comment?date=20040730

正規表現を超える - あどけない話
http://d.hatena.ne.jp/kazu-yamamoto/20090309/1236590230

正規表現ちっくなパーサーコンビネーター - あどけない話
http://d.hatena.ne.jp/kazu-yamamoto/20110131/1296466529

Applicativeのススメ - あどけない話
http://d.hatena.ne.jp/kazu-yamamoto/20101211/1292021817

Applicative よりも Monad の方が力が強い理由 - あどけない話
http://d.hatena.ne.jp/kazu-yamamoto/20100525/1274744955

Parsing with applicative functors in F# - Bug squash
http://bugsquash.blogspot.com/2011/01/parsing-with-applicative-functors-in-f.html

例によるApplicativeパーシング
http://www.mokehehe.com/realworldhaskell/index.php?Parsec%20%A5%D1%A1%BC%A5%B7%A5%F3%A5%B0%A5%E9%A5%A4%A5%D6%A5%E9%A5%EA#content_1_11

F# FParsec で(とりあえず)Forthを作る - 還暦プログラマの挑戦(Haskell に挑む→F#による言語造り)
http://www.cmas60.com/FS/fparsec.php


最後に

関数型的であり、ひじょーにCOOOOLなApplicativeスタイルではありますが、
当然ながら、使いこなすにはある程度の関数脳レベルが要求されるので、素人にはおすすめできない諸刃の剣。


ですから「F#でそれをやる意味あんの?」と問われたら、「慣れれば意味あるけど、慣れないうちは爆死必至だよ。」とでも答えよう。
そもそも、F#には、コンピューテーション式という、すばらしく可読性の高いステキスタイルがあらかじめ言語レベルで用意されているので、
あなたが、より意味を伝えるよう意識してコードを記述する善良なプログラマならば、わざわざApplicativeにする理由はこれっぽっちもない。
FParsecでのApplicativeスタイルは、まぎれもなく変態さん向け。変態さん個人で利用する場合、もしくは変態さんが
変態さんとコミュニケーションするためにはこの上ない良いツールです。それ以外のケースではうまく機能しないことは言うまでもありません。


なにはともあれ、FParsecは良い道具です。ご賞味あれ。

*1:FParsecもモナドだよとか言ったらその筋の人に怒られそうだけど、まぁ制限付モナドとして捉えるのは間違いじゃないんじゃないか

*2:便利だけど適用は割と難しい。主に学習コスト的な意味で。

*3:Parsec(Haskell)の場合だと、Eitherで返す

*4:「Real World Haskell」日本語翻訳版には、作用型解析という名前で紹介されています

F#で継続渡し形式(CPS)変換を抽象的に考えてみたら、それってつまりHaskellの継続モナドみたいなものでした。ということで継続ワークフロー(簡易版)作った。

[前置き]

継続とは

プログラミングにおいて「継続」とは、ある計算のある時点における「残りの計算」を表す概念のこと。
つまり、ある計算過程の瞬間におけるその過程の未来全体を表すものを意味する。
プログラミング言語で継続を扱う機能を導入すると、ユーザによるプログラムの実行順序の制御が可能になる。
プログラミングスタイルとして継続を表現したのが、継続渡しスタイルである。


詳しくは、下記のページを参照されたい。
なんでも継続
http://practical-scheme.net/docs/cont-j.html


継続渡し形式(CPS)とは

継続渡しスタイル(Continuation Passing Styleの頭文字をとってCPS)は、実際のプログラムの中では
継続をクロージャとしてコールバック関数として渡すような関数を定義するプログラミングスタイルのこと。



[本題]

F#で継続渡しスタイル(CPS)変換を抽象的に考えてみたら、それってつまりHaskellの継続モナドみたいなものでした。

檜山正幸のキマイラ飼育記 - CPS(継続渡し方式)変換をJavaScriptで説明してみるべ、ナーニ、たいしたことねーべよ
http://d.hatena.ne.jp/m-hiyama/20080116/1200468797

を読んでいて、「CPS変換を行うメタメタ関数」ってのが面白いなあと思いまして、
継続渡しスタイル(CPS)を抽象的に考えて、F#でもCPS変換を簡易的に行えるメタメタ関数を作ったら面白そうかなと考えていました。
しばらくして、そういえばHaskellにそんなんあったっけなあ・・。そこで思い出したのがHaskellの継続モナド

モナドのすべてより「Continuationモナド(継続モナド)」
http://www.sampou.org/haskell/a-a-monads/html/contmonad.html

上記リンク先の説明にあるように、継続モナドはCPSの計算を抽象的に表現しているモナドです。
継続渡しスタイルな関数(以下CPS関数)は、引数に関数をとり、自身の処理の最後でその関数(継続)を呼ぶというもので、CPS関数に継続として別のCPS関数を渡し、その別のCPS関数に継続として更に別のCPS関数を渡してゆく連鎖、つまりCPS関数のネストが、全体として継続渡しスタイルなプログラムを形成することになる。
CPS関数をモナドにくるんで >>=(bind関数)で繋ぐことで、継続モナドはCPS変換を行うメタメタ関数な表現を実現していると言える。


F#で継続ワークフロー(簡易版)を作ってみましょう
よろしいならばF#で継続ワークフローだ。
ということで、簡易なCPS変換をサポートする継続ワークフローを作ってみる。

ContinuationWorkflow.fs

#light
namespace Workflow

///継続ワークフロー
type ContinuationBuilder() = 
  ///CPS変換します
  member this.Return(a) = fun k -> k a
  ///継続バインド
  member this.Bind(m,f) = fun k -> m (fun a -> f a k)

module ContinuationWorkflow = 
 ///ContinuationBuilderを生成します
 let cps = new ContinuationBuilder()

たったこんだけです。基本的にHaskellの継続モナドからパクっただけみたいなってゆー(笑
モナドで包む部分とcallCCについては、よくわからない&面倒くさい&今回の主目的ではないので華麗にスルーしました。


ちなみに、Haskellの継続モナドの定義

newtype Cont r a = Cont { runCont :: ((a -> r) -> r) } 
  
instance Monad (Cont r) where 
    return a       = Cont $ \k -> k a                       
    (Cont c) >>= f = Cont $ \k -> c (\a -> runCont (f a) k) 

class (Monad m) => MonadCont m where 
    callCC :: ((a -> m b) -> m a) -> m a 
 
instance MonadCont (Cont r) where 
    callCC f = Cont $ \k -> runCont (f (\a -> Cont $ \_ -> k a)) k


継続ワークフローをお試し
では、さっそく継続ワークフローを味見してみる。

ContinuationWorkflowTest.fs

#light
open System
open Workflow.ContinuationWorkflow 

module CpsTest = 
 let Test1 =
  let f x = cps {return "F",x }
  let sharp (x,y) = cps {return  x + "#", y}
  let beginner (x,y) = cps {return y + "は" + x + "初心者です。"}
  let expert (x,y) = cps {return y + "は" + x + "達人です。"}

  //継続ワークフローで継続渡しスタイル (遅延評価)
  let fsharp x =
    cps {let! a = f x
         let! b = sharp a 
         match x with 
         "zecl"   -> let! c = beginner b
                     return lazy c
         | _      -> let! c = expert b
                     return lazy c
        }

  //お試し
  printfn "%s" <| fsharp "zecl" (fun x -> x.Force ())
  printfn "%s" <| fsharp "Don Syme" (fun x -> x.Force ())
  printfn ""


 let Test2 =
  //ふつうのフィボナッチ数列
  let rec fibo n =
    if n <= 1 then 1
    else fibo (n - 1) + fibo (n - 2)

  //継続渡しスタイルで書いたフィボナッチ数列
  let rec fiboCps n cont =
    if n <= 1 then cont 1
    else fun x -> fiboCps (n - 2) <| fun y -> cont (x + y)
         |> fiboCps (n - 1) 


  //継続渡しスタイルのフィボナッチ数列関数で周期 (遅延評価)
  let fiboCpsCycle n m =
    cps {let! a = fiboCps n 
         return lazy (a % m)
        }

  //ふつうのフィボナッチ関数を継続ワークフローで継続渡しスタイルに
  let fibo2 n = cps {return fibo n}
  //継続ワークフローでCPS変換したfibo2を使って周期 (遅延評価)
  let fibo2Cycle n m = 
    cps {let! a = fibo2 n
         return lazy (a % m)    
        }

  //print_anyして改行
  let panyn = print_any >> Console.WriteLine
  //お試し
  for i = 0 to 10 do (fibo i |> panyn)
  printfn ""
  for i = 0 to 10 do (fiboCps i (fun x -> panyn x))
  printfn ""
  for i = 0 to 10 do (fiboCpsCycle i 2 (fun x -> x.Force () |> panyn))
  printfn ""
  for i = 0 to 10 do (fibo2Cycle i 2 (fun x -> x.Force () |> panyn))

#if COMPILED
[<STAThread()>]
do
 CpsTest.Test1 
 CpsTest.Test2 
let key = Console.ReadKey ()
#endif


実行結果

zeclはF#初心者です。
Don SymeはF#達人です。

1
1
2
3
5
8
13
21
34
55
89

1
1
2
3
5
8
13
21
34
55
89

1
1
0
1
1
0
1
1
0
1
1

1
1
0
1
1
0
1
1
0
1
1


これはやさしいCPS変換サポーターですね。
脳内CPS変換が苦手な人にもかなり有用だと思います。継続ワークフロー(゚Д゚)ウマー!!!
ですが、多様は禁物。やはりご利用は計画的にといった感じでしょうか。


ちなみに、C#でもクロージャ(ラムダ式)が使えるので、C#で継続渡し形式っぽく
プログラムを書くことも可能といえば可能なんだけど、
可読性やパフォーマンスの観点から言って、C#でやる意味は皆無っぽいですね。

F#でStateモナドしてみよう。そうですよね、副作用は怖いですものね。

id:NobuhisaさんのWorkflowでモナド - (hatena (diary ’Nobuhisa))に触発されてF#でStateモナドしてみました。
ですが、Haskellのド素人でF#初心者なのでいろいろと間違っているかもしれません。


とりあえず的に、取り急ぎコードをうpしておきます。(F#CTP)
後日、加筆修正するつもりです。


F#でStateモナド

StateMonad.fs

#light
namespace Monad

///Stateモナドの器だよ
type State<'s, 'a> = State of ('s ->'a * 's);;

///StateBuilderだよ
type StateBuilder () = 
  member this.Bind(m, f) = State <| fun s -> let r = match m with
                                                      | State f -> f s
                                             match r with
                                              | (v,s) -> match f v with
                                                          | State f -> f s
                                                              
  member this.Return x = State <| fun s -> x, s
       
///Stateモナドの操作を提供するよ
module StateMonad =
  ///StateBuilderを生成するよ
  let state = StateBuilder () 
  ///状態を取得するよ。
  let Get = fun _ -> State <| fun s -> (s, s)
  ///状態を更新するよ
  let Put s = State <| fun _ -> ((), s)
  ///状態から取得した「値」に関数を適用するよ。(Getの高階関数版)
  let Gets f = state
                { 
                  let! s = Get ()
                  return (f s)
                }
  ///関数を適用して状態を更新するよ。(Putの高階関数版)
  let Modify f = state 
                  { 
                    let! s = Get ()
                    do! Put (f s)
                  }            
  ///Stateモナドを使って行った計算結果(コンテナ)を取得するよ。
  let Run (State s) = s
  ///評価した結果の値を取得するよ。
  let Eval (State s) = s >> fst
  ///実行された状態を取得するよ。
  let Exec (State s) = s >> snd

Stateモナドを試してみる

Program.fs

#light
open System
open Monad.StateMonad 

module StateTest = 
 type day =
   | None = 0x0000
   | Asa  = 0x0001
   | Hiru = 0x0002 
   | Ban  = 0x0004

 let panyn x = x |> (print_any >> Console.WriteLine)

 let Test1 =
//    let s = state 
//             { let! b = Get ()
//               do! Put (b - 5)
//               let! a = Get () 
//               return (b + 5) |> string
//             }
   let s = state 
            {
              let! b = Gets ((+)5) 
              do! Modify ((+) <| -5)
              return b |> string
            }
        
   let _ = s |> Run  <| 12 |> panyn 
   let _ = s |> Exec <| 12 |> panyn
   let _ = s |> Eval <| 12 |> panyn
   Console.WriteLine ()
   ()

 let Test2 =
  let s = state
           { let! s = Get ()
             let change (d:day option) =
                match d with
                 | Some (day.Asa)  -> Some <| day.Hiru
                 | Some (day.Hiru) -> Some <| day.Ban
                 | Some (day.Ban)  -> Some <| day.Asa
                 | _ -> None
                 
             do! Put (change s)  
             return match s with
                     | Some (day.Asa)  -> Some <| "おはよう"
                     | Some (day.Hiru) -> Some <| "こんにちわ"
                     | Some (day.Ban)  -> Some <| "こんばんわ"
                     | _ -> None
           }

  let _ = s |> Run  <| Some (day.Asa)  |> panyn
  let _ = s |> Run  <| Some (day.Hiru) |> panyn
  let _ = s |> Run  <| Some (day.Ban)  |> panyn
  let _ = s |> Run  <| Some (day.None) |> panyn
  Console.WriteLine ()
  let _ = s |> Exec <| Some (day.Asa)  |> panyn
  let _ = s |> Exec <| Some (day.Hiru) |> panyn
  let _ = s |> Exec <| Some (day.Ban)  |> panyn
  let _ = s |> Exec <| Some (day.None) |> panyn
  Console.WriteLine ()
  let _ = s |> Eval <| Some (day.Asa)  |> panyn
  let _ = s |> Eval <| Some (day.Hiru) |> panyn
  let _ = s |> Eval <| Some (day.Ban)  |> panyn
  let _ = s |> Eval <| Some (day.None) |> panyn
  ()
   
#if COMPILED
[<STAThread()>]
do
  StateTest.Test1 
  Console.WriteLine ()
  StateTest.Test2 
let key = Console.ReadKey ()
#endif


実行結果

("17", 7)
7
"17"

(Some "おはよう", Some Hiru)
(Some "こんにちわ", Some Ban)
(Some "こんばんわ", Some Asa)
(null, null)

Some Hiru
Some Ban
Some Asa
None

Some "おはよう"
Some "こんにちわ"
Some "こんばんわ"
None


あ、「///」でコメント付けたら、C#等のXMLコメントみたいに、
インテリセンス表示時にちゃんと説明が表示されるんですね。
今の今まで気付きませんでしたw

拡張ユークリッド互除法も再帰で書いた方が自然に見える

最適化のために最大公約数を求める必要があって、
ユークリッド互除法と拡張ユークリッド互除法を書いたので、チラ裏に残しておく*1


ユークリッド互除法

まずはよく見る実装をC#

        public static int Gcd(int a, int b)
        {
            while (b != 0)
            {
                int r = a % b;
                a = b;
                b = r;
            }
            return a;
        }

実にありがちな実装なのですが、どう見てもダサすぎ。


これってつまり、こういうことだよね。

        public static int GcdR(int a, int b)
        {
            if (b == 0) return a;
            return GcdR(b, a % b);
        }

スッキリしました。



何を思ったか、久々にHaskellでも書いてみることに。
って、実はHaskellでは最初からgcd関数が定義されていたりする。
http://www.zvon.org/other/haskell/Outputprelude/gcd_f.html


なので

main :: IO()
main = return (gcd 1029 1071) >>= print

これで、21って答えが出ちゃったりする。


でも、自分で書いてみることに意味があるよね。たぶん。

gcd2 :: Int -> Int -> Int
gcd2 a 0 = a
gcd2 a b = gcd2 b (rem a b)

こんだけ。書くまでもなかった。



拡張ユークリッド互除法

よく見る実装をC#で。

        public static int GcdEx(int a, int b)
        {
            int x1 = 0;
            int x2 = 1;
            int y1 = 1;
            int y2 = 0;
            int r1 = b;
            int r2 = a;

            while (true)
            {
                if (r1 == 0) return r2;

                //Math.DivRemなんてのもあるけど
                int q = (int)Math.Floor((double)(r2 / r1));
                int r = r2 % r1;

                int x = x2 - q * x1;
                int y = y2 - q * y1;

                if (r == 0) break;

                x2 = x1;
                x1 = x;
                y2 = y1;
                y1 = y;
                r2 = r1;
                r1 = r;
            }
            return r1;
        }

うへ。これはひどい・・・。
なんというローカル変数の乱立www


これも再帰で表現したほうがスッキリできそうです。
実際に書いてみると、こんな感じでした。

        private delegate TResult Func<T1, T2, T3, T4, T5, T6,TResult>(T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6);
        public static int GcdExR(int a, int b)
        {
            Func<int, int, int, int, int, int, int> f = null;
            f = (a2, b2, w2, x2, y2, z2) =>
            {
                if (b2 == 0) return a2;
                return f(b2, a2 % b2, x2, (w2 - a2) / b2 * x2, z2, (y2 - a2) / b2 * z2);
            };
            return f(a, b, 1, 0, 0, 1);
        }

いざ書いてみると、引数を6つとるFuncがないことに気付く…w
一見わかりにくそうに見えるかもしれませんが、個人的には上に書いたよく見る実装よりも、
こちらの方が好みです。書き方的に「今風」っぽいというのも含めて。
あと、ラムダ式ばっか書いてると、匿名delegateでの旧式な書き方を忘れ気味であることに気付いた俺ガイル。


締めにHaskellでも

gcdex :: Int -> Int -> Int
gcdex a b = gcdex' a b 1 0 0 1
  where
    gcdex' a 0 _ _ _ _ = a
    gcdex' a b w x y z = gcdex' b r y z (w-y*q) (x-z*q)
      where (q, r) = quotRem a b

こうしてみると、やっぱHaskellはクールだなあ。

*1:負の数に関しては考えていません

Project Euler第1問目

Project Eulerはじめました。(冷やし中華的な意味で)


問題

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.


10未満の自然数のうち、3 もしくは 5 の倍数になっているものは 3, 5, 6, 9 の4つがあり、これらの合計は 23 になる。
同じようにして、1,000 未満の 3 か 5 の倍数になっている数字の合計を求めよ。
http://projecteuler.net/index.php?section=problems&id=1


Haskellでの解答

{-Project Euler Q001-}
main :: IO()
main = return (answer [1..999]) >>= print

answer :: [Int] -> Int
answer = sum . map judge

judge :: Int -> Int
judge x
    | x `mod` 3 ==  0 || x `mod` 5 ==  0 = x
    | otherwise = 0 


C#3.0での解答

using System;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            (from num in Enumerable.Range(1, 999)
             where num % 3 == 0 || num % 5 == 0
             select num).Aggregate(Enumerable.Repeat(default(int), 1), (nums, i)
                                   => from num in nums
                                      select num + i
                                   ).ToList().ForEach(i => Console.WriteLine(i.ToString()));
            Console.ReadLine();
        }
    }
}

実行結果

233168

暇をみて、解けるものだけでも解いていこうと思います。へっぽこなりに(^ω^;)

いまさら聞けないアルゴリズム その1「ハノイの塔」 Part.2

前回に引き続きまして、Haskell版「ハノイの塔」も書いてみた。

{-ハノイの塔-}
main :: IO ()
main = putStr $ concat $ [write x | x <- hanoi "A" "B" "C" 3]  

hanoi :: String -> String -> String -> Int -> [(String, String)]
hanoi _ _ _ 0 = []
hanoi a b c n =  hanoi a c b (n - 1) ++ [(a, b)] ++ hanoi c b a (n - 1)

write :: (String, String) -> String
write x =  (fst x) ++ " -> " ++ (snd x) ++ "\n"

実行結果

A -> B
A -> C
B -> C
A -> B
C -> A
C -> B
A -> B

やっぱり、再帰は関数型言語の方が断然美しく書ける感じだ。
というか、ひさびさにHaskell書いたら若干忘れかけてた(^ω^;)

Haskellで虫食い算的なもの

後日LINQで解いてみた、LINQで虫食い算的なものはこちらです。


虫食い算を解く

{-虫食い算-}
--    4○○
--   × ○○
--   --------
-- ○○○○○
-- ※ただし、○には4を除く0〜9の数字を1つずつ使用
--   また、桁の先頭は0ではないこと。

import Control.Monad

num = [0..3]++[5..9]

main :: IO ()
main = return answer >>= print

answer :: [[Int]]
answer = do a <- num
            b <- num  ; guard $ notElem b [a]
            c <- num  ; guard $ notElem c [a,b] ; guard $ c /= 0
            d <- num  ; guard $ notElem d [a,b,c]
            e <- num  ; guard $ notElem e [a,b,c,d] ; guard $ e /= 0
            f <- num  ; guard $ notElem f [a,b,c,d,e]
            g <- num  ; guard $ notElem g [a,b,c,d,e,f]
            h <- num  ; guard $ notElem h [a,b,c,d,e,f,g]
            i <- num  ; guard $ notElem i [a,b,c,d,e,f,g,h]
            guard $ toint [4,a,b] * toint [c,d] == toint [e,f,g,h,i]
            return $ map toint [[4,a,b],[c,d],[e,f,g,h,i]]

toint :: [Int] -> Int
toint = foldl (\ i j -> 10*i+j) 0 


実行結果

[[402,39,15678],[495,36,17820]]

はいよくできましたー