Standard ML で簡易タイマを書いてみた

概要

time(1) コマンドや、Common Lisp の time 関数 のような、 処理全体にどのくらい時間がかかっているのか教えてくれる関数が Standard ML の標準ライブラリになさそうだった。なので自分でいい加減なものを書いてみた。

幸い、実行時間を測るタイマ自体は標準ライブラリが提供してくれていた。 なので「書いてみた」といっても、標準ライブラリの呼び出しているだけ。

コード

fun time f = let
    val realt = Timer.startRealTimer()
    val rv = f ()
    val elapsed = Timer.checkRealTimer realt
in
    (Time.toMilliseconds elapsed, Time.toMicroseconds elapsed, rv)
end;

使い方

これは、こんな感じで使う。

fun hoge arg1 arg2 = ...;

val (milli, micro, rv) = time (fn () => hoge n m);

このように、測りたい関数を呼び出す、引数のない関数を time に渡すだけである。 time 関数は

  • 関数を処理するのにかかった時間 (単位: ミリ秒)
  • 同上 (単位: マイクロ秒)
  • 関数の戻り値

の三つ組を返す。

ここまで

使い方の具体例とか必要?

Standard ML のコードをリアルタイムにチェックする (Emacs + Flycheck + MLton)

必要なもの

  1. Emacs
  2. Flycheck
  3. MLton

手順

init.el に以下のように書いておく。

(require 'flycheck)

(flycheck-define-checker mlton
  "Standard ML type and syntax checking with mlton compiler.
See URL 'http://mlton.org"
  
  :command ("mlton" "-stop" "tc" source)

  :error-patterns
  ((error line-start "Error: " (file-name) blank line ?\. column ?\. "\n"
      (message (1+ line-start (1+ blank) (+ nonl) "\n"))))
  :mode (sml-mode))
  
(add-to-list 'flycheck-checkers 'mlton)

経緯

κeen 氏によるこちらの記事

SMLSharpを使ってSMLのon-the-flyエラーチェック

を読んでいて、MLton にも型チェックだけするオプションがなかったっけ、と思ったので MLton を使った文法チェッカを Flycheck に登録するコードを書いてみた。

氏は Emacs に標準でついている Flymake を用いてチェッカを書かれているのに対して、 私がインストールの必要な Flycheck を用いているのは、単にこちらの方が登録コードを書くのが簡単そうだったからである。

ハマったとこ

だが、実際はかなり試行錯誤した。
特に複数行にわたるエラーメッセージを取得するやりかたはなかなかわからなかった。

行の開始 line-start と対になり、行の終わりを意味すると思われる line-end が、 行末だけではなく、どうもパースの終わりをも意味しているようで、 line-end とマッチするとそれ以降のエラーメッセージの解析を行わない。

なので、行末とマッチさせるために代わりに "\n" を用いた。 これが正解なのかはわからない。

パターンを直しては M-x flycheck-compile (C-c ! C-c) をして確認、また直しては確認、 という手順を繰り返した。

課題

処理系依存。MLton をインストールしなければならない。

Poly/ML と SML/NJ についてもざっと調べたけれど、これらの処理系は最初のエラーを見つけるとそこでチェックをやめてしまい、「コード全体をみて複数のエラーを列挙する」という MLton のような挙動をしてくれないようだった。

相方「そういえば、へびやんさん、きんモザ観てないんですか」
わたし「観てない」
相方「やっぱり。リアクションないから、あれ、と思ってたんですよ」
わたし「?」


観た


わたし「なんでやねん! なんで先週みてなかったんや。
…… ! ………… !? …… ? …… (錯乱)」

たらい回し関数を書いてみる(1)

前説

たらい回し関数は有名な三引数の再帰関数で、膨大な回数呼ばれる関数です。
関数呼び出しのベンチマークとして使われるそうです。
いろいろな言語でこの関数を実装して実行にかかる時間を計ります。

$ time echo "20 10 0" | ./tarai

引数を標準入力から受け取れるようにします。見ての通り空白で区切って、一行の文字列にしておきます。


たらい回し関数は、三つ目の引数の計算を、必要になるまで遅らせることで実行にかかる時間が短くなることが知られています。
三つ目の引数を整数そのものではなく、整数を返す関数にすることで計算するタイミングを調節できます。


たらい回し関数をこういう風に書くことで、

  • 標準入出力とのやりとり
  • 関数、無名関数の書き方
  • 条件分岐の書き方

を確認することができるので、たらい回し関数は Hello, world の次のお題としていいんじゃないかと個人的に思います。

OCaml

let rec tarai x y z =
  if x <= y then
    y
  else
    let fz = z () in
    let nx = tarai (x - 1) y z in
    let ny = tarai (y - 1) fz (fun () -> x) in
    let nz () = tarai (fz - 1) x (fun () -> y)
    in
    tarai nx ny nz

let () =
  Printf.printf "%d\n"
                (Scanf.scanf "%d %d %d "
                             (fun x y z ->
                              tarai x y (fun () -> z)))

ocamlopt を使うと実行可能なバイナリを生成できます。

$ ocamlopt -o tarai_ocaml tarai.ml
$ ls -lh tarai_ocaml
-rwxr-xr-x  1 hebiyan  staff   690K  1 22 00:13 tarai_ocaml*

サイズを小さくする以外の最適化に関するオプションはなさそう。


実行します。

$ time echo "20 10 0" | ./tarai_ocaml
20

real    0m0.007s
user    0m0.001s
sys     0m0.003s

思ったこと。

  • コンパイルしたときに型の間違いがわかるのはありがたい
  • 継続を渡す scanf が便利
  • 再起関数には再起関数であることを表すキーワードが必要