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

この記事では「FixLangで遊ぼう」の第4回として、loop関数、共用体、構造体について説明したいと思います。

本記事は[FixLangの公式ドキュメント](https://github.com/tttmmmyyyy/fixlang/blob/main/Document.md)をもとに、初学者向けに翻訳して適宜書き改めたものです。詳しく知りたい方は公式ドキュメントをご覧ください。

過去の記事は、こちらをご覧ください。
- [FixLangで遊ぼう (1): FixLangの紹介](https://qiita.com/pt9999/items/9bc57a735d79579a4ecc)
- [FixLangで遊ぼう (2) 基礎編: モジュールと型](https://qiita.com/pt9999/items/fc0af6c593a753bb3717)
- [FixLangで遊ぼう (3) 基礎編: 基本的な文法](https://qiita.com/pt9999/items/fa99b6c8e5e9f1daf10c)


## loop関数

FixLang には、多くの言語で見られるような `for` 文や `while` 文は存在しません。代わりに、組み込みの `loop` 関数を利用してループ処理を実行します。(なお、ループ処理は再帰関数や `fix` 関数を使っても実現可能です)

`loop` 関数は、以下のような構文で記述します。
```haskell
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. 「初期状態」を「状態変数」に格納し、「ループ処理本体」を実行します。
1. 「ループ処理本体」が `continue(次の状態)` を返した場合、「次の状態」を「状態変数」に格納し、「ループ処理本体」を再度実行します。
1. 「ループ処理本体」が `break(処理結果)` を返した場合、`loop` 関数を中断し、「処理結果」を返します。

### loop関数の例
```haskell
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に似ていますが、バリアント(種類)ごとに異なる型の付属情報を持つことができます。

共用体を定義するには、下記の構文を使用します。

```haskell
type 共用体名 型パラメータ... = union {
   バリアント名_1: バリアント型_1,     // 種類その1
   バリアント名_2: バリアント型_2,     // 種類その2
   ...
   バリアント名_n: バリアント型_n      // 種類そのn
};
```
共用体の定義では次のことを注意してください。
- 共用体名の最初の文字は大文字にする必要がある
- 型パラメータは0個以上で空白で区切って記述する
- 型パラメータの最初の文字は小文字にする必要がある
- バリアント定義とバリアント定義の間には `,` が必要
- 最後のバリアント定義の後にある`,`は無視される
- 最後にセミコロンが必要

### 共用体の例 (`LoopResult`)
例えば、`loop`関数の説明に現れた `LoopResult s b` は2つの型パラメータ `s` と `b` を備えた共用体として定義されています。
```haskell
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`型は次のように定義できます。
```haskell
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で複数の情報をひとまとめにするには、構造体を使用します。

### 構造体を定義する

構造体を定義するには、下記の構文を使用します。

```haskell
type 構造体名 型パラメータ... = struct {
    フィールド名_1: 型_1,
    フィールド名_2: 型_2,
    ...,
    フィールド名_n: 型_n
};
```
例:
```haskell
type User = struct {
    name: String,        // 名前 (String型)
    age: I64             // 年齢 (I64型)
};
```

構造体の定義では次のことを注意してください。
- 構造体名の最初の文字は大文字にする必要がある
- 型パラメータは0個以上で空白で区切って記述する
- 型パラメータの最初の文字は小文字にする必要がある
- フィールド定義とフィールド定義の間には `,` が必要
- 最後のフィールド定義の後にある`,`は無視される
- 最後にセミコロンが必要

### 構造体の値を構築する

構造体の値を構築するには、下記の構文を使用します。
```haskell
構造体名 { 
    フィールド名_1: 値_1,
    フィールド名_2: 値_2,
    ...,
    フィールド名_n: 値_n
}
```
例:
```haskell
let user = User {
  name: "taro",       // 名前
  age: 20             // 年齢
};
```

### 構造体のフィールドの値を取得・設定・更新する

構造体のフィールドの値を取得するには、`@フィールド名` という関数を使用します。`@フィールド名`の型は、`構造体型 -> フィールド型` です。
```haskell
eval *println(user.@taro);          // "taro" と出力される
eval *println(user.@age.to_string); // "20" と出力される
```

構造体のフィールドの値を設定するには、`set_フィールド名` という関数を使用します。`set_フィールド名`の型は、`フィールド型 -> 構造体型 -> 構造体型` です。
```haskell
let user = user.set_name("jiro");    // name を "jiro" に変更した新しい構造体が返る
let user = user.set_age(23);         // age を 23 に変更した新しい構造体が返る
```
構造体のフィールドの値を更新するには、`mod_フィールド名` という関数を使用します。`mod_フィールド名`の型は、`(フィールド型 -> フィールド型) -> 構造体型 -> 構造体型` です。
```haskell
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 に変更した新しい構造体が返る
```

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