FixLangで遊ぼう (2) 基礎編: モジュールと型

この記事では「FixLangで遊ぼう」の第2回として、FixLangのモジュールと型について説明したいと思います。前回の記事は[こちら](https://qiita.com/pt9999/items/9bc57a735d79579a4ecc)をご覧ください。

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

FixLangを試しに使ってみたい方はぜひ[fix playground](https://tttmmmyyyy.github.io/fixlang-playground/)にアクセスしてみてください。

また、[FixLangの非公式Dockerイメージ](https://hub.docker.com/r/pt9999/fixlang)を公開していますので、Dockerに慣れている方はこちらの方が便利かも知れません。

## ソースファイル

ソースコードを記述するファイルのことを、ソースファイルと呼びます。
FixLangのソースファイルは、`.fix` という拡張子にします。(例: `main.fix`)

FixLangのソースファイルを実行するには、シェルプロンプトで以下のコマンドを実行します。

```
$ fix run -f main.fix
```

複数のソースファイルがある場合は、以下のように `-f` の後に複数のソースファイルを指定します。
```
$ fix run -f main.fix sub.fix
```

## モジュール

```haskell
module Main;                       // モジュール定義

main: IO ();                       // mainの型定義
main = println("Hello World!");    // mainの値定義
```

ソースファイルの先頭では、モジュールを定義する必要があります。
モジュールとは、ソースファイル内部の型定義や値定義などをまとめたものです。

上記の例では、`Main`という名前のモジュールを定義しています。
モジュール名の先頭文字は大文字にする必要があります。

なお、モジュール階層を表現するため、`Main.Model.Impl`のようなモジュール名にすることも可能です。この場合、`.` 区切りの名前の先頭文字は大文字にする必要があります。

### グローバル値
上記の例では、`main`というグローバル値の型と値を定義しています。
FixLangでは、グローバル値の型を明示的に定義する必要があります。

FixLangのプログラムを実行すると、`Main`モジュールの`main`という名前の関数を実行します。

### 名前空間
```haskell
module Main;                           // モジュール定義

namespace Test {                       // 名前空間の定義
    test: I64 -> I64 -> String;        // testの型定義
    test = |x, y| (x + y).to_string;   // testの値定義
}

main: IO ();                           // mainの型定義
main = println(Test::test(3,5));       // mainの値定義
```
モジュール内部では、名前空間を定義可能です。
名前空間の役割は、以下の通りです。
- 関連した機能をひとまとめにできる
- 名前の衝突を防ぐことができる

上記の例の`Test::test`は、名前空間を明示的に指定した名前です。
名前が他の名前と衝突していない場合や、前後の文脈から推定できる場合は、名前空間を省略可能です。従って、上記の例の`Test::test`は単に`test`と書くことも可能です。

FixLangの組み込みライブラリでは、`Array`, `Iterator`などの名前空間が定義されています。詳しくは、[組み込みライブラリのリファレンス](https://github.com/tttmmmyyyy/fixlang/blob/main/BuiltinLibraries.md)をご覧ください。

## 型
FixLangでは値はすべて型を持ちます。型は数学の集合で、値は集合の要素と見なすことができます。
### 数値型

|  |8ビット|16ビット|32ビット|64ビット|
|:-:|:-:|:-:|:-:|:-:|
|符号なし整数|U8|U16|U32|U64|
|符号あり整数|I8|I16|I32|I64|
|浮動小数点数|-|-|F32|F64|

数値リテラルは、`{数値を表す文字列}_{型}` という形式です。
型を省略すると、ピリオドがない場合は I64、ピリオドがある場合は F64 になります。
```haskell
123              // 64ビット符号あり整数(I64)
123_U32          // 32ビット符号なし整数(U32)

123.45           // 64ビット浮動小数点数(F64)
123.45_F32       // 32ビット浮動小数点数(F32)
```

また、`0x`, `0o`, `0b` というプレフィックスを付けることで、16進数/8進数/2進数での表記が可能です。
```haskell
0xdeadbeef_U32    // 32ビット符号なし整数(U32), 16進数表記
0o000755_U16      // 16ビット符号なし整数(U16), 8進数表記
0b00010111_U8     // 8ビット符号なし整数(U8), 2進数表記
```
### 真偽値型
`Bool`型は真偽値を表します。`true`, `false`は真偽値を表すリテラルです。

```haskell
true             // 真
false            // 偽
```

### 配列型
`Array a`は配列型を表します。例えば文字列(`String`)の配列は `Array String` になります。また、64ビット符号あり整数(`I64`)の配列は `Array I64` になります。
`[` と `]` の間に要素を `,` で区切って書くと、配列のリテラルになります。なお、配列の要素はすべて同じ型にする必要があります。
```haskell
[1, 2, 3]                  // Array I64 型
[65_U8, 66_U8, 67_U8]      // Array U8 型
["Hello", "World"]         // Array String 型
```
最後の要素の後にある`,`は無視されます。
```haskell
// 以下の2つは同じ配列を表す
[1, 2, 3]
[1, 2, 3, ]
```
要素の型が異なる場合はエラーになります。
```haskell
[1, 2, "Hello"]            // 要素の型が異なるためエラー
```
### 文字列型
`String`型は文字列を表します。文字列のリテラルは `"Hello"`のように書くことができます。

```haskell
"Hello"              // "Hello" という文字列
"Foo\nbar\nbaz"      // Foo, bar, baz を改行で連結した文字列
```
文字列にはヌル文字(`0_U8`)を含めることはできません。

FixLangの内部では文字列はバイト配列として表現されています。エンコーディングは特に決まっていませんが、アプリケーションではUTF-8として扱うことが多いと思います。

### タプル型
`(` と `)` の間に2個以上の要素を `,` で区切ったものはタプルになります。
タプルの各要素は異なる型であっても問題ありません。
```haskell
(1, 2)                    // (I64, I64)型
(3, "Hello")              // (I64, String)型
(4.5_F32, 6_U8, [7, 8])   // (F32, U8, Array I64)型
```

`(` と `)` の間に1個の要素があるものは、1個の要素をカッコで囲んだものとして解釈されます。
```haskell
(1)                       // I64 型
("Hello")                 // String型
```

`(` と `)` の間に1個の要素と`,`があるものは、長さ1のタプルとして解釈されます。
```haskell
(1,)                       // (I64,) 型
("Hello",)                 // (String,) 型
```

### ユニット型
`(` と `)` の間に何も無いものはユニット型になります。ユニット型は`()`というただ一つの値しか持ちません。
```haskell
()                        // ()型
```

### IO a 型
画面入出力やファイル入出力などのI/O処理は `IO a`型になります。
ただし `a` はI/O処理結果の型です。
画面出力のように処理結果がとくにない場合は `IO ()`型になります。
`Main`モジュールの`main`関数は`IO ()`型にする必要があります。
```haskell
input_line                // IO String 型
println("Hello")          // IO () 型
```

### 関数型
関数型は `引数の型 -> 戻り値の型` のように表します。

下記の例は、`I64 -> String` という型を持つ関数定義です。(関数定義については、次回の記事で詳しく説明する予定です)
```haskell
make_item: I64 -> String;
make_item = |id| (
  "item-" + id.to_string
);
```

また、2個の引数を持つ関数の型は `1番目の引数の型 -> 2番目の引数の型 -> 戻り値の型` のように表します。

下記の例は、`String -> I64 -> String` という型を持つ関数定義です。

```haskell
make_item2: String -> I64 -> String
make_item2 = |name, id| (
  name + "-" + id.to_string
); 
```

ちなみに、`->` 演算子は右結合のため、`String -> I64 -> String` は `String -> (I64 -> String)` として解釈されます。

FixLangには、「2変数関数」という概念はありません。引数は1個ずつ順番に適用されます。

例えば、上記の `make_item2` 関数に `"button"`という文字列を適用すると、`I64 -> String` 型の関数になります。
その関数に `123`を適用すると、`String`型の値になります。(上記の例では、`"button-123"`という文字列になります)

関数定義と関数適用については、次回の記事で詳しく説明する予定です。

## 終わりに
本記事では、FixLangのモジュールと型について説明しました。
次回の記事で、FixLangの基本的な文法について説明する予定です。