Mokkosuの始め方 (関数型言語経験者向け)

ここではF#, OCaml, Haskellなどの関数型プログラミング言語経験者を 対象としてMokkosuでのプログラミングについて説明します。

はじめに

MokkosuはHindley-Milner型推論(Value Restrictionあり)を行う 正格評価の関数型プログラミング言語です。 コンパイラが実装されており、プログラムは.NETのIR(中間言語)に コンパイルされます。 MokkosuはF#, OCaml, Haskellから多くの機能を引き継いでいます。

Mokkosuの最初の実装は、2015年2月5日から2015年2月18日までの わずか2週間の期間で開発されました。 2週間の期間で言語の設計、コンパイラの実装、簡易統合開発環境の実装、 ライブラリの実装、最低限のドキュメントの執筆に取り組みました。 取り組みの過程はブログに記事としてまとめてあります。

二週間という限られた期間で作ったためいくつかの機能は なくなくあきらめざるを得ませんでした。 代表的なものに型クラスがあります。 現時点ではMokkosuは劣化版F#のような言語ですが、 いずれマルチパラメータの型クラスと関数従属性を実装予定であり、 そうなると既存のプログラミング言語との差別化が図れるのではないかと 考えています。 目指すのは次世代の関数型プログラミング言語です。

Hello, World.

以下は画面に"Hello, World."と表示するMokkosuプログラムです。

# Hello.mok
do msgbox "Hello, World.";

1行目はコメントです。Mokkosuには#から改行文字までの単一行コメントと、 #[から#]までの複数行コメントが利用できます。複数行コメントは 入れ子にできません。

2行目は画面に表示を行う部分でdo文を使って記述されています。 do文は()型(ユニット型)になる式を評価してその結果(()値)を 捨てます。msgboxは以下の型をもつ関数で、値(整数、浮動小数点数、 文字列、文字に限定される)を画面に表示します。

msgbox : α -> ()

この型の表記から分かるように、Mokkosuでは多相型はαのような ギリシャ文字で表します。

do文のキーワードのdoは省略することができます。

関数の定義

以下はMokkosuで階乗を計算する関数を書いた例です。

fun fact = { 
  1 -> 1;
  n -> n * fact (n - 1);
};

{}で囲まれた部分はブロックと呼ばれる構文で、 引数を受け取ってその値をパターンマッチさせる関数を生成します。 ちょうどOCamlのfunction式と同じ働きをします。

以下の例はブロックを使って、リストの1を2に2を1に置き換える 関数を記述した例です。

let list = map { 1 -> 2; 2 -> 1; x -> x } [1, 2, 3, 2, 1]

実行するとlist[2, 1, 3, 1, 2]というリストになります。

このままではOCamlのfunction文が簡潔になっただけという印象を 受けるかもしれませんが、ブロックでは既存の関数型言語の パターンマッチではできない以下のようなことが可能です。

fun f = {
  1 -> 1;
  let a = 123;
  2 -> a;
  3 -> a + 1;
  do "DEBUG: 1, 2, 3 以外";
  n -> n
};

上の関数fは1のときは1を返し、2のときは123、3のときは124を返し、 それ以外の時は入力をそのまま返します。

上のプログラムでは、2のケースと3のケースで共通に使う定数123 を変数aに束縛しています。 このように、パターンマッチの途中で変数束縛や関数定義を許すことで、 利用可能な値の範囲を限定することが可能となります。

また、パターンマッチの途中でdoを使って任意の式を実行できます。 上の例ではデバッグメッセージを表示しています。

基本型としてInt型、Double型、String型、Char型、 Bool型、()型があります。

Mercuryはリストは[1,2,3]のように表記し、 型は[Int]のように表記します。 タプルは(1, "abc")のように表記し、型は(Int, String)のように 表記します。

以下は連想リストから対応する値を探し出す関数のMercuryでの定義例です。

fun lookup x = {
  [] -> error "Not Found";
  (y, v) :: _ ? x == y -> v;
  _ :: ys -> lookup x ys;
}

コンスパターンはパターン :: パターン?以降の式はパターンガードです。 lookupは初めての2引数の関数です。 MokkosuではF#、OCaml、Haskellのように関数は自動的に カリー化されます。

Mokkosuでは代数的データ型を利用することができます。 以下は連結リストの定義例です。

type List<T> = Nil | Cons(T, List<T>);

以下はList<T>型のリスト2つを連結する関数の定義例です。

fun app list1 list2 =
  match list1 {
    ~Nil -> list2;
    ~Cons(x, rest) -> Cons(x, app rest list2)
  };

パターンの最初についている~は、 これがユーザ定義のタグであることを表しています。 Mokkosuではタグに大文字で始まる文字列以外に、 小文字で始まる文字列も指定することができます。 変数とタグを明確に区別できるように、 明示的に~を書かせる文法となっています。

関数matchは1引数目の値を2引数目の関数に適用する関数です。

リスト内包表記

以下はリスト内包表記を使って、 ピタゴラス数を求めるプログラムの例です。

let pythagoras =
  for x <- 1 .. 10;
      y <- 1 .. 10;
      z <- 1 .. 10;
      if x * x + y * y == z * z;
  in (x, y, z)

上のプログラムは以下のコードに変換されて実行されます。

__for_bind (1 .. 10) (\x ->
__for_bind (1 .. 10) (\y ->
__for_bind (1 .. 10) (\z ->
if  x * x + y * y = z * z -> __for_unit (x, y, z)
else __for_zero
)))

そのため、__for_bind__for_unit__for_zeroを 再定義することで、for式の動作をカスタマイズすることができます。

同様なカスタマイズは演算子についても行えます。 例えば、2 + 3という式は、

__operator_pls 2 3

という式に展開されます。そのため、__operator_pls関数を 再定義することで(+)演算子の動作をカスタマイズできます。

次のステップ

Mokkosuがどのような言語であるかお分かりいただけたでしょうか。 より深く理解するためのリソースとして以下のものがあります。

あなたのオリジナルの作品をお待ちしています。