1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FixLangで遊ぼう (4) 基礎編: loop関数、共用体、構造体

Posted at

この記事では「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 関数は以下のように実行されます。

  1. 「初期状態」を「状態変数」に格納し、「ループ処理本体」を実行します。
  2. 「ループ処理本体」が continue(次の状態) を返した場合、「次の状態」を「状態変数」に格納し、「ループ処理本体」を再度実行します。
  3. 「ループ処理本体」が 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つの型パラメータ sb を備えた共用体として定義されています。

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 に変更した新しい構造体が返る

終わりに

今回の記事では、ループ関数、共用体、構造体について説明しました。
次回の記事では、イテレータと参照カウンタについて説明する予定ですが、もしかすると別の内容にするかもしれません。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?