この記事では「FixLangで遊ぼう」の第4回として、loop関数、共用体、構造体について説明したいと思います。
本記事はFixLangの公式ドキュメントをもとに、初学者向けに翻訳して適宜書き改めたものです。詳しく知りたい方は公式ドキュメントをご覧ください。
過去の記事は、こちらをご覧ください。
loop関数
FixLang には、多くの言語で見られるような for
文や while
文は存在しません。代わりに、組み込みの loop
関数を利用してループ処理を実行します。(なお、ループ処理は再帰関数や fix
関数を使っても実現可能です)
loop
関数は、以下のような構文で記述します。
loop(
初期状態, |状態変数|
ループ処理本体
)
loop
関数は、s -> (s -> LoopResult s b) -> b
という型を持ちます。ここで、s
はループ状態を表す型パラメーターで、「初期状態」や「状態変数」の型を示します。b
はループ処理の結果を表す型パラメーターで、loop
関数の返り値となります。
「ループ処理本体」では、LoopResult s b
型の値を返す必要があります。この値はループを継続するか中断するかを表すものです。LoopResult
型の値を構築するには、以下の関数のいずれかを使います。
- ループを継続する場合:
continue : s -> LoopResult s b
- ループを中断する場合:
break : b -> LoopResult s b
loop
関数は以下のように実行されます。
- 「初期状態」を「状態変数」に格納し、「ループ処理本体」を実行します。
- 「ループ処理本体」が
continue(次の状態)
を返した場合、「次の状態」を「状態変数」に格納し、「ループ処理本体」を再度実行します。 - 「ループ処理本体」が
break(処理結果)
を返した場合、loop
関数を中断し、「処理結果」を返します。
loop関数の例
calc_fib: I64 -> I64;
calc_fib = |n| (
let arr = Array::fill(n, 0);
let arr = arr.set!(0, 1);
let arr = arr.set!(1, 1);
let arr = loop((2, arr), |(idx, arr)|
if idx == arr.get_size {
break $ arr
} else {
let x = arr.@(idx-1);
let y = arr.@(idx-2);
let arr = arr.set!(idx, x+y);
continue $ (idx+1, arr)
}
);
arr
);
上記の例では、loop
関数を利用してフィボナッチ数列を計算しています。
ループの初期状態は (2, arr)
です。ループ処理本体は状態変数として (idx, arr)
というタプルを取ります。idx
は配列の添字、arr
はフィボナッチ数列を格納する配列です。このように、タプルを状態変数にすれば、複数の情報をループで扱うことができます。
idx
が配列サイズ未満の場合、フィボナッチ数列の値を計算してarr
に格納し、continue $ (idx+1, arr)
を返してループ処理を続行します。
idx
が配列サイズに達した場合、break $ arr
を返してループ処理を抜けます。loop
関数の返り値はフィボナッチ数列を格納した配列になります。
共用体(union)
共用体を使うと、さまざまなバリアント(種類)のどれかであるようなデータを表現できます。
例えば、果物の種類(りんご、みかん、いちご)や処理結果(正常終了、エラー終了)などは共用体で表現できます。
共用体は、C言語のenumに似ていますが、バリアント(種類)ごとに異なる型の付属情報を持つことができます。
共用体を定義するには、下記の構文を使用します。
type 共用体名 型パラメータ... = union {
バリアント名_1: バリアント型_1, // 種類その1
バリアント名_2: バリアント型_2, // 種類その2
...
バリアント名_n: バリアント型_n // 種類そのn
};
共用体の定義では次のことを注意してください。
- 共用体名の最初の文字は大文字にする必要がある
- 型パラメータは0個以上で空白で区切って記述する
- 型パラメータの最初の文字は小文字にする必要がある
- バリアント定義とバリアント定義の間には
,
が必要 - 最後のバリアント定義の後にある
,
は無視される - 最後にセミコロンが必要
共用体の例 (LoopResult
)
例えば、loop
関数の説明に現れた LoopResult s b
は2つの型パラメータ s
と b
を備えた共用体として定義されています。
type LoopResult s b = union {
continue : s, // ループ継続 (`s`型の値を持つ)
break : b // ループ中断 (`b`型の値を持つ)
};
LoopResult s b
型の値は、continue
または break
のどちらかです。continue
は付属情報としてs
型の値を持ち、break
は付属情報としてb
型の値を持ちます。型を集合と見なすと、LoopResult s b
型はs
型とb
型の直和になります。
共用型を定義すると、いくつかの基本的なメソッド関数が自動的に定義されます。
-
{バリアント名}: {バリアント型} -> {共用体型}
: バリアントの値から共用体を構築する。 -
is_{バリアント名}: {共用体型} -> Bool
: 共用体が指定されたバリアントかどうか判定する。 -
as_{バリアント名}: {共用体型} -> {バリアント型}
: 共用体が指定されたバリアントの場合、バリアントの値を取得する。それ以外の場合はパニックする(すなわち、エラーメッセージを印字してプログラムの実行を停止する)。
例えば、先ほどの例のように LoopResult s b
を定義した場合、以下の関数がLoopResult
名前空間に自動的に定義されます。
-
continue : s -> LoopResult s b
:s
型の値からLoopResult
値を構築する。 -
break : b -> LoopResult s b
:b
型の値からLoopResult
値を構築する。 -
is_continue : LoopResult s b -> Bool
:LoopResult
値がcontinue
で構築されたものかどうかを判定する。 -
is_break : LoopResult s b -> Bool
:LoopResult
値がbreak
で構築されたものかどうかを判定する。 -
as_continue : LoopResult s b -> s
:LoopResult
値がcontinue
で構築された場合、s
型の値を抽出する。continue
でない場合はパニックする。 -
as_break : LoopResult s b -> b
:LoopResult
値がbreak
で構築された場合、b
型の値を抽出する。break
でない場合はパニックする。
共用体の例その2 (Option
)
共用体の別の例として、Option
型があります。Option
型は、存在しない可能性がある値を表すために使用されるものです。
(他の言語では、オブジェクトが存在しないときに null を使うことがありますが、FixLangではOption
型を使います)
Option
型は次のように定義できます。
type Option a = union {
none : (), // 値が存在しない
some : a // 値が存在する (`a`型の値を持つ)
};
上記の定義によって、以下の関数が Option
名前空間に自動的に定義されます。
-
none : () -> Option a
:()
型の値からOption
値を構築する。 -
some : a -> Option a
:a
型の値からOption
値を構築する。 -
is_none : Option a -> Bool
:Option
値がnone
で構築されたものかどうかを判定する。 -
is_some : Option a -> Bool
:Option
値がsome
で構築されたものかどうかを判定する。 -
as_none : Option a -> ()
:Option
値がnone
で構築された場合、()
型の値を抽出する。none
でない場合はパニックする。 -
as_some : Option a -> a
:Option
値がsome
で構築された場合、a
型の値を抽出する。some
でない場合はパニックする。
なお、none
を使ってOption
値を構築するには、none()
と記述する必要があります。
(f() == f(())
という糖衣構文を思い出してください)
構造体
FixLangで複数の情報をひとまとめにするには、構造体を使用します。
構造体を定義する
構造体を定義するには、下記の構文を使用します。
type 構造体名 型パラメータ... = struct {
フィールド名_1: 型_1,
フィールド名_2: 型_2,
...,
フィールド名_n: 型_n
};
例:
type User = struct {
name: String, // 名前 (String型)
age: I64 // 年齢 (I64型)
};
構造体の定義では次のことを注意してください。
- 構造体名の最初の文字は大文字にする必要がある
- 型パラメータは0個以上で空白で区切って記述する
- 型パラメータの最初の文字は小文字にする必要がある
- フィールド定義とフィールド定義の間には
,
が必要 - 最後のフィールド定義の後にある
,
は無視される - 最後にセミコロンが必要
構造体の値を構築する
構造体の値を構築するには、下記の構文を使用します。
構造体名 {
フィールド名_1: 値_1,
フィールド名_2: 値_2,
...,
フィールド名_n: 値_n
}
例:
let user = User {
name: "taro", // 名前
age: 20 // 年齢
};
構造体のフィールドの値を取得・設定・更新する
構造体のフィールドの値を取得するには、@フィールド名
という関数を使用します。@フィールド名
の型は、構造体型 -> フィールド型
です。
eval *println(user.@taro); // "taro" と出力される
eval *println(user.@age.to_string); // "20" と出力される
構造体のフィールドの値を設定するには、set_フィールド名
という関数を使用します。set_フィールド名
の型は、フィールド型 -> 構造体型 -> 構造体型
です。
let user = user.set_name("jiro"); // name を "jiro" に変更した新しい構造体が返る
let user = user.set_age(23); // age を 23 に変更した新しい構造体が返る
構造体のフィールドの値を更新するには、mod_フィールド名
という関数を使用します。mod_フィールド名
の型は、(フィールド型 -> フィールド型) -> 構造体型 -> 構造体型
です。
let user = User { name: "taro", age: 20 };
let user = user.mod_name(|name| "SUPER " + name); // name を "SUPER taro" に変更した新しい構造体が返る
let user = user.mod_age(add(1)); // age を 21 に変更した新しい構造体が返る
終わりに
今回の記事では、ループ関数、共用体、構造体について説明しました。
次回の記事では、イテレータと参照カウンタについて説明する予定ですが、もしかすると別の内容にするかもしれません。