FParsec のチュートリアル的な何か

さて、今日は FParsec 日本語チュートリアルを読んでやってみるよ。

nuget はインストールしてるかな?
私は nuget3 をインストールしてみたよ:

% yaourt -S nuget3

勉強用のディレクトリを作成しよう:

% mkdir ./hello-fparsec
% cd ./hello-fparsec

FParsec をインストールしてみよう:

% nuget install FParsec -outputDirectory ./packages

-outputDirectory ./packages を付けない場合、カレントディレクトリに FParsec がインストールされてしまうので注意だ。
なので、Visual Studio の慣習に習って ./packages/ にインストールしてみた。

さっそく使ってみる

run 関数に何らかのパーサー関数と文字列を渡すと結果が返ってくる模様。

ʕ•͡ω•ʔ pfloat は FParsec 浮動小数点数をパースして浮動小数点数を返す組み込みのパーサー関数だぞ

// demo000.fsx
open FParsec


let test p str =
  match run p str with
    | Success(result      , _, _)   -> printfn "Success: %A" result
    | Failure(errorMessage, _, _)   -> printfn "Failure: %s" errorMessage

test pfloat "1.25"
test pfloat "1.25E"
test pfloat "1.25E 3"

それで、こんな風に打ってみると:

% fsharpi -I ./packages/FParsec.1.0.2/lib/net40-client/ -r "FParsec.dll" ./demo000.fsx
Success: 1.25
Failure: Error in Ln: 1 Col: 6
1.25E
     ^
Note: The error occurred at the end of the input stream.
Expecting: decimal digit

Failure: Error in Ln: 1 Col: 6
1.25E 3
     ^
Expecting: decimal digit

最初の test pfloat "1.25" だけパースされた。

ヽ(=´▽`=)ノ

OCaml の Test::Simple を目コピしてみた その 4

OCamlソースコードに沿って TAPDocument.ml を test-document.fs にして、TAPBuilder.ml を test-builder.fs に書いてみた。

なんかクラスっぽいやつでやってみたんだけど、これでいいのかな…………(汗

module Test.Builder

type Plan(?count : int) =
  let current_plan : option<int> = count

type TestCaseBuilder () =
  let mutable running_tests : (int -> Test.Document.node) list = [];

  member this.BuildTestCase(todo : option<string>, diag : option<Test.Document.diagnostic>, test : bool, description : string) =
    let running_test = fun number ->
      match todo with
        | None          -> Test.Document.TestCaseNode((if test then Test.Document.Ok else Test.Document.NotOk),
                                                      number,
                                                      description,
                                                      diag)
        | Some x        -> Test.Document.TodoTestCaseNode((if test then Test.Document.Ok else Test.Document.NotOk),
                                                          number,
                                                          description,
                                                          Test.Document.Todo(x),
                                                          diag)
    running_tests <- running_test :: running_tests
    running_test

  member this.BuildDiagnostic(line : string) =
    let running_test = fun (_ : int) ->
                       Test.Document.DiagnosticNode (Test.Document.Diag( [ line ] ))
    running_tests <- running_test :: running_tests
    running_test

今ブログ記事を書いてる時に気がついたけど、これじゃダメだよねwwwwww

だって、Plan とか let plan = Plan(12) とかってしないとわかんないじゃんwwwwww ほげーーーーーーー!!!

あと、F# では、関数に省略した引数を使えないので、typeコンストラクタ?の引数に使わなければならない………

(。・_・。) ………

TestCaseBuilderTestCaseResult とかにして、そのリストをどこかに持ったほうが良いのかも?
こんな感じ:

type TestCase(?todo, ?diag, test, description) =
  let running_test = fun number ->
      match todo with
        | None          -> Test.Document.TestCaseNode((if test then Test.Document.Ok else Test.Document.NotOk),
                                                      number,
                                                      description,
                                                      diag)
        | Some x        -> Test.Document.TodoTestCaseNode((if test then Test.Document.Ok else Test.Document.NotOk),
                                                          number,
                                                          description,
                                                          Test.Document.Todo(x),
                                                          diag)

OCaml の Test::Simple を目コピしてみた その 3

続き。

ʕ•͡ω•ʔ TAPDocument.ml 完全目コピでけたー!

init_document はなんか初期化しそう。

let init_document doc =
  let count     = count_tests         doc in
  let failures  = count_test_failures doc in
  match doc with
  | Document(PlanNode(plan)::nodes) ->
     Document(
         PlanNode(plan)::nodes
         @
           (if count = plan then [] else [ (create_count_footer count plan) ] )
         @
           (if failures = 0 then [] else [ (create_failure_footer count failures) ])
       )
  | Document(nodes)                 ->
     Document(
         nodes
         @
           [ PlanNode(count) ]
         @
           (if failures = 0 then [] else [ (create_failure_footer count failures) ])
       )

後は文字列化する関数。

そんでさ、function Document(nodes) -> は Warning 出るけど、どーすれば回避できるかわかんないって書いたけど、わかった。
let hoge Document nodes = ... でいいみたい。
引数が複数あってわけわかんない場合は let piyo (Document nodes) = みたいに括弧つけとこう。

let string_of_status status =
  match status with
  | OK          -> "ok"
  | NotOk       -> "not ok"
let string_of_diagnostic (Diag lines) =
  List.map (fun line -> "# " + line + "\n") lines
  |> List.fold (+) ""

こことかかっこよく |>(パイプライン) つこうた

let string_of_directive (Todo s) =
  "# TODO " + s
let string_of_node node =
  let emit_diagnostic diag =
    (match diag with
     | None          -> "\n"
     | Some diag     -> "\n" + (string_of_diagnostic diag))
  in
  match node with
  | TestCaseNode(status, num, desc, diag)          ->
     (string_of_status status)
     + " "
     + num.ToString()
     + " - "
     + desc
     + (emit_diagnostic diag)
  | TodoTestCaseNode(status, num, desc, dir, diag) ->
     (string_of_status status)
     + " "
     + num.ToString()
     + " - "
     + desc
     + (string_of_directive dir)
     + (emit_diagnostic diag)
  | DiagnosticNode(diag)                           ->
     (string_of_diagnostic diag)
  | PlanNode(count)                                ->
     "1.." + count.ToString() + "\n"
let string_of_document (Document nodes) =
  List.map string_of_node nodes
  |> String.concat "" 

ここも。

全部のソースはここに置いてある。

OCaml の Test::Simple を目コピしてみた その 2

続き。

OCaml では文字列の結合は ^ だけど*1、F# では + だ。
また、OCaml での string_of_int は F# には存在しないけど、書くとしたらこんな感じになる:

let string_of_int (n:int):string =
  n.ToString()

DiagnosticNode を返す関数 2 連発:

let create_failure_footer test_count failure_count =
  DiagnosticNode(
    Diag(["Looks like you failed "
          + failure_count.ToString()
          + " tests of "
          + test_count.ToString()
          + " run."])
    )
let create_count_footer test_count plan_count =
  DiagnosticNode(
    Diag(["Looks like you planed "
          + plan_count.ToString()
          + " tests but "
          + (if test_count < plan_count then
               ("only ran " + test_count.ToString())
             else
               ("ran " + ((test_count - plan_count).ToString()) + " extra"))
          + " ."])
    )

*1:F# でも Warning は出るものの、使うことができる

OCaml の Test::Simple を目コピしてみた

これ

普通に OCaml のコードがコンパイルできるのすごい。

モジュール名は Test.Builder にしておく。

module Test.Builder

type status =
  | NotOk
  | Ok

type directive =
  | Todo of string

type diagnostic =
  | Diag of string list

type node =
  | TestCaseNode     of status * int * string * diagnostic             option
  | TodoTestCaseNode of status * int * string * directive * diagnostic option
  | DiagnosticNode   of diagnostic
  | PlanNode         of int

type test_document =
  | Document of node list

テストケースをノードとして管理?する。
test_document が一番トップの型みたいだ。

let rec count_test_nodes nodes count =
  match nodes with
    | []                                   -> count
    | TestCaseNode(_, _, _, _)::xs
    | TodoTestCaseNode(_, _, _, _, _)::xs  -> count_test_nodes xs count + 1
    | DiagnosticNode(_)::xs
    | PlanNode(_)::xs                      -> count_test_nodes xs count

関数 count_test_nodes はノードの数を数える。
具体的には TestCaseNodeTodoTestCaseNode を数える。

let count_tests = function Document(nodes) ->
  count_test_nodes nodes 0

count_tests は引数が test_document だった時用の count_test_nodes だ。

function Document(nodes) -> は Warning を発生させる。
この Warning をどうやって回避すれば良いのかは不明である。

let count_test_failures = function Document(nodes) ->
  let rec loop nodes count =
    match nodes with
    | []
      -> count
    | TestCaseNode(Ok, _, _, _)::xs
    | TodoTestCaseNode(Ok, _, _, _, _)::xs
    | DiagnosticNode(_)::xs
    | PlanNode(_)::xs
      -> loop xs count
    | TestCaseNode(NotOk, _, _, _)::xs
    | TodoTestCaseNode(NotOk, _, _, _, _)::xs
      -> loop xs count + 1
  in loop nodes 0

関数 count_test_failures は指定されたノードリストの中から失敗したノードの数を数える。

書いたのは ここ に置いとく。

F# で Either もできたよー

ʕ•͡ω•ʔ 最近 fsharp-mode より tuareg-mode のがいいんじゃないかって思うようになってきた

module Data.Either

type either<'a, 'b> =
  | Left  of 'a
  | Right of 'b

let either f g e =
  match e with
    | Left  a    -> f a
    | Right b    -> g b

let isLeft x =
  match x with
  | Left _    -> true
  | Right _   -> false

let isRight x =
  match x with
  | Left _    -> false
  | Right _   -> true

let lefts es =
  Seq.filter isLeft es

let rights es =
  Seq.filter isRight es

F# で Maybe できたよーーー

ʕ•͡ω•ʔ Haskell の何かを F# で書くと楽しいんじゃ(天才博士風)

というわけで、実装して覚えるシリーズ。

型の定義がこちら:

type Maybe<'a> =
  | Nothing
  | Just of 'a

F# 風にするなら、Maybemaybe にするべきかも。

ゆかいな Maybe 関連の関数:

let maybe a f m =
  match m with
    | Just x     -> f x
    | Nothing    -> a

let isJust m =
  match m with
    | Nothing    -> false
    | Just _     -> true

let isNothing m =
  match m with
    | Nothing    -> true
    | Just _     -> false

let fromJust x =
  match x with
    | Nothing    -> failwith "Maybe.fromJust: Nothing"
    | Just x     -> x

だけど、F# には option というのが既にあるので、そっちを使いましょう。

FSharp やってみた2

その 2。

add という関数を定義して、引数を渡して評価してみる:

> let add x y =
-   x + y;;
val add : x:int -> y:int -> int

> add 2 2;;
val it : int = 4

関数のボディ部分を折ってるけど、インデントしないと Warning が出るので注意。

あと、終了する時は:

> exit 0;;

か:

> #q;;

でできる。

アノテーション

フレーズを渡したら、leet 語っぽく返してくれる関数を定義してみよう:

> let toHackerTalk phrase =                          
-   phrase.Replace( 't', '7' ).Replace( 'o', '0' );; 

    phrase.Replace( 't', '7' ).Replace( 'o', '0' );;
  --^^^^^^^^^^^^^^
/home/rihine/workspace/hello-fsharp/stdin(4,3): error FS0072: Lookup on object of
indeterminate type based on information prior to this program point. A type annotation 
may be needed prior to this program point to constrain the type of the object. 
This may allow the lookup to be resolved.

エラーになった。
どうやら、引数 phraseobject っぽく解釈されてるものの、object には当然 Replace() なんてメソッドはないわけで、 そんなもん無いよって言ってるらしい。

アノテーションを付けてみるといいらしい:

> let toHackerTalk (phrase:string) =                
-   phrase.Replace( 't', '7' ).Replace( 'o', '0' );;
val toHackerTalk : phrase:string -> string

関数は実際、値

ソースコードをファイルに書いて実行することにする。
引数を 4 倍する関数を定義する:

// quadruple.fsx
let quadruple x =
  let double x =
    x * 2

  double ( double x );;

printfn "%d" (quadruple 4);;

関数の中で関数を定義できる。という感じ。

普通にファイル名を指定するだけで実行してくれる:

% fsharpi ./quadruple.fsx
16

関数を引数に取る高階関数を使うコード:

// chrisTest.fsx
let chrisTest test =
  test "Chris"
;;

let isMe x =
  if x = "Chris" then
    "it is Chris!"
  else
    "it's someone else"
;;
    
printfn "%A" (chrisTest isMe);;

ラムダ関数

前の add 関数はラムダ式で書くとこう書ける:

> let add = (fun x y -> x + y);;
val add : x:int -> y:int -> int

その2:

> let twoTest test =
-   test 2;;
val twoTest : test:(int -> 'a) -> 'a

> twoTest (fun x -> x < 0);;
val it : bool = false

参照

FSharp やってみた

良い感じのブラウザだと Try F# で式を評価できるらしいんだけど、できないブラウザだったので、処理系をインストールして式を評価してみるよ。

インストール

yaourt で fsharp をインストールできるよ。

% yaourt -S fsharp

REPL 起動

REPL を起動する時は fsharpi って打ってね:

% fsharpi

れっと

let を使って変数?を定義?することができるよ:

> let lucky = 3 + 4;;
val lucky : int = 7

あと、式の最後には ;;(セミコロン 2 つ)を付けてね。
前に定義してた変数?も参照できるよ:

> let unucky = lucky + 6;;
val unucky : int = 13

とりあえず変数?を定義?して 別の値を設定することもできるよ。
シャドウイングってやつ?

> let duplicated = "original value";;
val duplicated : string = "original value"

> let duplicated = "new value";;     
val duplicated : string = "new value"

れっと☆みゅーたぶる

let した変数?は二度と変更することができないよ。
どうしても変更したい変数?を使う時は let mutable で定義?するといいみたい:

> let mutable modifiable = "original value";;
val mutable modifiable : string = "original value"

> modifiable <- "new value";;
val it : unit = ()

> modifiable;;               
val it : string = "new value"

「代入」する場合は <-(左向き矢印)を使うみたい。

型推論されて int とか float とか string になってるー!!! というやつ。

> let anInt = 10;;
val anInt : int = 10

> let aFloat = 20.0;;
val aFloat : float = 20.0

> let aString = "I'm a string!";;
val aString : string = "I'm a string!"

出力

へろーわーるどするときは printfn を使うよ:

> printfn "Hello, World from F# REPL!!";;
Hello, World from F# REPL!!
val it : unit = ()

printf みたいに書式をあれしてこれできる:

> printfn "The answer is %d" 42;;
The answer is 42
val it : unit = ()

Lisp でいうと:

(printfn "The answer is %d" 42)
;; | The answer is 42
;; => nil 

(๑´ڡ`๑) おしまい

参照