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 

(๑´ڡ`๑) おしまい

参照

フォントを Roboto にした

タイトル下

前やったときは記事上とかにしてたんだけど、これだと複数になっちゃうかも?って思ってタイトルの下に書いた。

<!-- タイトル下 //-->
<link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Roboto+Mono' rel='stylesheet' type='text/css'>
<!--// タイトル下 -->

デザイン CSS

常日頃からソースコードとかのフォントおかしいなーと思っていたので、Roboto Mono にすることにした。

/* <system section="theme" selected="wideboard"> */
@import "/css/theme/wideboard/wideboard.css";
/* </system> */

/* <system section="background" selected="fff"> */
body {
    background: #fff;
    font-family: 'Roboto', sans-serif;
}
/* </system> */

/* <customize!!> */
.entry-content pre {
    font-family: 'Roboto Mono', sans-serif;
}
/* </customize!!> */

(๑´ڡ`๑) いい感じ

後日談

結論から言うと、VSCode では C# なプロジェクトを作れないのだ。ということがわかった。
ASP.NET なのしかサポートしてないので、やりたいなら Visual Studio コミュニティーエディションをインストールしろということらしい*1

実のところ、このような:

- task:
    name: build
    is-shell-command: true
    show-output: true
    args: [ -c hello.c -o hoge ]
    command: gcc

タスクを記述する外部 SDL では*2依存関係を記述できない。
このことは JS 製のタスクランナーやビルドツールが古典的なビルドツールである make を超えることができない理由の一つである*3

私に必要なのは、CLI なプロジェクト管理ツールである。

% dry add ./src/hello.c

とかやると、.velbet/tasks.yml とかに hello.c が追加されるような仕組みである。
ビルドする時は:

% dry build

とかやるとビルドできるのだ。

*1:英語が読めないけど空気は読める人が導き出した結論

*2:ツールを記述する言語とは異なる言語を使用する DSL のこと

*3:多分

VSCode を yaourt でいんすとろーるしてみた。

まだ良くわかっていない*1
ビルドタスクは以下のような .vscode/tasks.json に書くらしい。

{
    // See http://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "0.1.0",
    "command": "dotnet",
    "isShellCommand": true,
    "args": [],
    "tasks": [
        {
            "taskName": "build",
            "args": [ ],
            "isBuildCommand": true,
            "showOutput": "silent",
            "problemMatcher": "$msCompile"
        }
    ]
}

複数のファイルをビルドするときはどうするのだろう?とかとりあえずは 1 つのファイルをビルドしたいのだけれど、 どーすれば良いのだろうなどと考えていた。

これを見た時、「ふーむ、こんな書き方で依存関係を記述できるものなのだろうか…」と思った。
さて、ここで大昔に考えたビルドランナーの velvet(仮)に登場して貰う。

タスクを記述した例えば .velvet/tasks.yml みたいなファイルに下記のように書いてあったとする:

- task:
    name: build
    is-shell-command: true
    show-output: true
    args: [ -c hello.c -o hoge ]
    command: gcc

.vscode/tasks.jsonYAML に直しただけ(あと必要なさそうなのを削った)だけれど、

% velvet build

あるいは:

% velvet

で実行できるといいなーと思う。

こういう書き方だと、1 タスクにつき、1 コマンドでなんとも生産性が悪そうだ。
(。・_・。) うーん………。もうちょっと調べてみるか……

*1:C# なコードをどーやってビルドすれば良いのか、とか