FixLangで遊ぼう (3) 基礎編: 基本的な文法

この記事では「FixLangで遊ぼう」の第3回として、FixLangの基本的な文法(let式, if式など)について説明したいと思います。
FixLangのモジュールと型については、[第2回](https://qiita.com/pt9999/items/fc0af6c593a753bb3717)を参照してください。

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

## let式

ローカル変数を定義するには、let 式を使用します。
```haskell
let 変数 = 式1 in 式2
```
上記の構文では、まず式1を評価して変数を初期化し、その変数を使って式2を評価します。変数のスコープは式2の内部です。
式2の評価結果がlet式の値になります。

また、以下のように `in` の代わりに `;` を書いても同じ意味になります。ふだんはこちらを使うことが多いです。
```haskell
let 変数 = 式1; 式2
```
let式が長い場合は、適当な場所で改行してインデントし、カッコをつけることをお勧めします。インデントやカッコは必須ではありませんが、可読性を高めるために役立ちます。
```haskell
let sixty_four = (
    let n = 3 + 5;
    n * n
);
sixty_four + sixty_four
```
なお、変数のスコープは式2の内部だけなので、以下のように再帰関数を定義することはできません。
(式1ではまだ`fact`が初期化されていないため、`fact`を使用できません)
```haskell
let fact = (
  |n| if n <= 1 { 1 } else { n * fact(n - 1) }    // 式1
);
fact(5)                                           // 式2
```
ローカルで再帰関数を定義するには、組み込み関数の `fix` を利用します。

```haskell
let fact = (
  fix $ |loop, n| if n <= 1 { 1 } else { n * loop(n - 1) }
);
fact(5)
```

<details><summary>let式の説明からは逸脱するため、fix関数について詳しく知りたい方はここをクリックしてください。</summary>

`fix` は、`((a -> b) -> a -> b) -> a -> b` という型を持つ不動点関数です。
`fix` に `(a -> b) -> a -> b`型の関数`f`を適用した結果 `fix(f)` は、`a -> b` 型の関数です。
`fix(f)`は、関数`f`の不動点になります。つまり、`fix(f) == f(fix(f))` が成り立ちます。

上記のサンプルコードでは、`fact` は `fix $ |loop, n| (以下略)` を評価した値になります。
ここで、`f = |loop, n| (以下略)` とおくと、`fact == fix(f)` が成り立ちます。
従って、`fact == fix(f) == f(fix(f)) == f(fact)` が成り立ちます。
従って、`fact` は `|loop, n| (以下略)` の `loop` に `fact` そのものを代入した関数になります。
このように、組み込み関数の`fix`を使うことで、ローカルで再帰関数を定義可能です。
</details>

## if式
if式の構文は次の通りです。
```haskell
if 条件式 { 式1 } else { 式2 }
```
上記の構文では、まず条件式を評価します。
条件式がtrueの場合、式1を評価します。条件式がfalseの場合、式2を評価します。
式1または式2の評価結果がif式の値になります。
注意点として、条件式の型はBoolにする必要があります。また、式1と式2は同じ型にする必要があります。

また、以下のように `else { 式2 }` の代わりに `; 式2` と書いても同じ意味になります。
```haskell
if 条件 { 式1 }; 式2
```
上記の構文では、式2の周りに波カッコをつける必要がないため、ネストを減らすことができます。
主に、エラー等の条件で処理途中で抜けるときに利用します。
可読性を高めるため、`;` の後に改行を入れるのをおすすめします。

以下に例を示します。
```haskell
sum_up_to: I64 -> Result ErrMsg I64;
sum_up_to = |n| (
   if n < 0 { err $ "`n` should be a non-negative number" };
   if n > 10000 { err $ "`n` is too large" };
   ok $ Iterator::range(0, n).sum
);
```

## 関数適用
関数`f`に値`x`を適用するには、`f(x)`と書きます。

前にも書きましたが、FixLang には「2変数関数」や「3変数関数」を表す専用の型はありません。
その代わり、`a -> b -> c` という型を、「`a`の値と`b`の値を引数に取り、`c`の値を返す2変数関数」のようなものと見なすことができます。
(`->` 演算子は右結合のため、上記の型は `a -> (b -> c)` として解釈されます) 

例えば、`multiply : I64 -> I64 -> I64` という「2変数関数」を考えてみます。これに`3`を部分適用した `multiply(3) : I64 -> I64` は、整数を3倍する「1変数関数」になります。従って `multiply(3)(5)` の結果は `15` になります。なお、`f(x, y)`は`f(x)(y)`と等価な糖衣構文であるため、最後の式は `multiply(3, 5)` と書くことができます。

また、特別な構文として、`f()`と書いた場合、`f(())`として解釈されます。つまり、関数`f`にユニット値`()`を適用したものになります。

## 関数定義
「1変数関数」の値を作成するには、以下のように書きます。これは、他の言語ではラムダ式やクロージャと呼ばれるものです。仮引数のスコープは関数本体の式の内部です。
```haskell
|仮引数| 関数本体の式
```
「2変数関数」の値を作成するには、以下のように書きます。
```haskell
|仮引数1, 仮引数2| 関数本体の式
または
|仮引数1| |仮引数2| 関数本体の式
```

グローバル値の型と値を定義することで、グローバルな「関数」を定義できます。
関数本体の式が長い場合、可読性を高めるため、関数本体をカッコで囲み、適当な場所で改行するのをお勧めします。
```haskell
fizzbuzz: I64 -> String;
fizzbuzz = |n| (
    if n % 15 == 0 { "FizzBuzz" };
    if n % 3 == 0 { "Fizz" };
    if n % 5 == 0 { "Buzz" };
    n.to_string
);
```

FixLang の関数は、関数定義の外で定義された値を「キャプチャ」できます。
```haskell
test_message: String -> String;
test_message = |message| (
    let show_mes = |n| (
      n.to_string + ":" + message + "\n";
    );
    show_mes(1) + show_mes(2) + show_mes(3)
);
```
上記の例では、`test_message`関数の内部で`show_mes`関数がローカル定義されています。`show_mes`関数は関数外部で定義された`message`の値をキャプチャしています。
FixLangではすべてのオブジェクトは不変であるため、キャプチャした値も不変です。このため、関数の動作が変化することはありません。
例えば、以下のようにローカル関数の定義後に変数を再定義しても、キャプチャした値は変化しません。
```haskell
test_message: String -> String;
test_message = |message| (
    let show_mes = |n| (
      n.to_string + ":" + message + "\n";
    );
    let message = message + "xxx";   // ここでmessageを再定義してもキャプチャした値は変化しない
    show_mes(1) + show_mes(2) + show_mes(3)
);
```


## `.` 演算子

関数に値を適用する別の方法として`.` 演算子があります。
`.` 演算子は `x.f == f(x)` として定義されます。

### `.`演算子の優先度

`.` 演算子の優先度は、カッコによる関数適用の優先度よりも低いです。
従って、`obj.method(arg)`は `method(arg, obj)`として解釈されます。

```haskell
// method関数が次のように型定義されていたとします。
method: Param -> Obj-> Result;   
...
// このとき、以下の(1)〜(4)はすべて同じ結果になります。
// (通常は(1)または(4)の書き方が適切です)
let x = obj.method(arg);    // (1)
let x = obj.(method(arg));  // (2)
let x = method(arg)(obj);   // (3)
let x = method(arg, obj);   // (4)
```

### `.`演算子の例

```haskell
let arr = [1, 2, 3];
println(arr.get_size)       // "3" と出力される
```
配列のサイズを取得するには、`arr.get_size` と書きます。
`get_size`の型は `Array a -> I64`です。この関数に配列を適用すると、配列のサイズが返ってきます。
なお、`arr.get_size()`と書くと、これは `get_size((), arr)`として解釈されるため、エラーになります。間違いやすいためご注意ください。

```haskell
let arr = ["Hello", "World"];
let arr = arr.set!(1, "FixLang");
println(arr.@(0) + arr.@(1))       // "HelloFixlang" と出力される
```
配列の要素を変更するには、`arr.set!(idx, elem)` のように書きます。
`set!`の型は `I64 -> a -> Array a -> Array a` です。この関数にインデックスと要素と配列を適用すると、更新後の配列が返ってきます。

配列の要素を取得するには、`arr.@(idx)` のように書きます。
`@`の型は `I64 -> Array a -> a` です。この関数にインデックスと配列を適用すると、要素が返ってきます。


## `$` 演算子

関数に値を適用する別の方法として`$` 演算子があります。
`$` 演算子は `f $ x == f(x)` として定義されます。

`$` 演算子は右結合です。つまり、`f $ g $ x == f(g(x))` となります。

### `$` 演算子の優先度

`$` 演算子の優先度は他のどの演算子よりも低いです。
このため、`$` 演算子はカッコを減らすために利用できます。

```
println $ (1, 2).to_string
// println((1, 2).to_string) と解釈される

println $ if condition { "Hello" } else { "World" }
// println(if condition { "Hello" } else { "World" }) と解釈される
```

## パターン
let式、関数式はいずれもローカル名を導入します。
ローカル名の型がタプルまたは構造体の場合、渡された値を分解するためにパターンを利用できます。

以下にlet式でタプルを分解するパターンの例を示します。
```
let tuple = ("Hello", 123);  // タプルを定義する
let (a, b) = tuple;          // a は "Hello", b は 123 になる
```

以下に関数式でタプルを分解するパターンの例を示します。
```
myfunc: (String, I64) -> String;
myfunc = |(str, num)| str + num.to_string;

main: IO();
main = println $ myfunc $ ("Hello", 123);   // "Hello123" と出力される
```

上記の`myfunc`は、`(String, I64)`型の引数を受け取る「1引数関数」です。
一方、下記の`myfunc2`は、`String`型の引数と`I64`型の引数を受け取る「2引数関数」です。どちらの形もよく使われますが、混同しやすいのでご注意ください。
```
myfunc2: String -> I64 -> String;
myfunc2 = |str, num| str + num.to_string;

main: IO();
main = println $ myfunc2("Hello", 123);   // "Hello123" と出力される
```

## 終わりに
本記事では、FixLangの基本的な文法について説明しました。
次回の記事では、ループ処理、共用体、構造体について説明する予定です。