この記事では「FixLangで遊ぼう」の第2回として、FixLangのモジュールと型について説明したいと思います。前回の記事はこちらをご覧ください。
本記事はFixLangの公式ドキュメントを日本語に翻訳し、初学者向けに一部を書き改めたものです。詳しく知りたい方は公式ドキュメントをご覧ください。
FixLangを試しに使ってみたい方はぜひfix playgroundにアクセスしてみてください。
また、FixLangの非公式Dockerイメージを公開していますので、Dockerに慣れている方はこちらの方が便利かも知れません。
ソースファイル
ソースコードを記述するファイルのことを、ソースファイルと呼びます。
FixLangのソースファイルは、.fix
という拡張子にします。(例: main.fix
)
FixLangのソースファイルを実行するには、シェルプロンプトで以下のコマンドを実行します。
$ fix run -f main.fix
複数のソースファイルがある場合は、以下のように -f
の後に複数のソースファイルを指定します。
$ fix run -f main.fix sub.fix
モジュール
module Main; // モジュール定義
main: IO (); // mainの型定義
main = println("Hello World!"); // mainの値定義
ソースファイルの先頭では、モジュールを定義する必要があります。
モジュールとは、ソースファイル内部の型定義や値定義などをまとめたものです。
上記の例では、Main
という名前のモジュールを定義しています。
モジュール名の先頭文字は大文字にする必要があります。
なお、モジュール階層を表現するため、Main.Model.Impl
のようなモジュール名にすることも可能です。この場合、.
区切りの名前の先頭文字は大文字にする必要があります。
グローバル値
上記の例では、main
というグローバル値の型と値を定義しています。
FixLangでは、グローバル値の型を明示的に定義する必要があります。
FixLangのプログラムを実行すると、Main
モジュールのmain
という名前の関数を実行します。
名前空間
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
などの名前空間が定義されています。詳しくは、組み込みライブラリのリファレンスをご覧ください。
型
FixLangでは値はすべて型を持ちます。型は数学の集合で、値は集合の要素と見なすことができます。
数値型
8ビット | 16ビット | 32ビット | 64ビット | |
---|---|---|---|---|
符号なし整数 | U8 | U16 | U32 | U64 |
符号あり整数 | I8 | I16 | I32 | I64 |
浮動小数点数 | - | - | F32 | F64 |
数値リテラルは、{数値を表す文字列}_{型}
という形式です。
型を省略すると、ピリオドがない場合は I64、ピリオドがある場合は F64 になります。
123 // 64ビット符号あり整数(I64)
123_U32 // 32ビット符号なし整数(U32)
123.45 // 64ビット浮動小数点数(F64)
123.45_F32 // 32ビット浮動小数点数(F32)
また、0x
, 0o
, 0b
というプレフィックスを付けることで、16進数/8進数/2進数での表記が可能です。
0xdeadbeef_U32 // 32ビット符号なし整数(U32), 16進数表記
0o000755_U16 // 16ビット符号なし整数(U16), 8進数表記
0b00010111_U8 // 8ビット符号なし整数(U8), 2進数表記
真偽値型
Bool
型は真偽値を表します。true
, false
は真偽値を表すリテラルです。
true // 真
false // 偽
配列型
Array a
は配列型を表します。例えば文字列(String
)の配列は Array String
になります。また、64ビット符号あり整数(I64
)の配列は Array I64
になります。
[
と ]
の間に要素を ,
で区切って書くと、配列のリテラルになります。なお、配列の要素はすべて同じ型にする必要があります。
[1, 2, 3] // Array I64 型
[65_U8, 66_U8, 67_U8] // Array U8 型
["Hello", "World"] // Array String 型
最後の要素の後にある,
は無視されます。
// 以下の2つは同じ配列を表す
[1, 2, 3]
[1, 2, 3, ]
要素の型が異なる場合はエラーになります。
[1, 2, "Hello"] // 要素の型が異なるためエラー
文字列型
String
型は文字列を表します。文字列のリテラルは "Hello"
のように書くことができます。
"Hello" // "Hello" という文字列
"Foo\nbar\nbaz" // Foo, bar, baz を改行で連結した文字列
文字列にはヌル文字(0_U8
)を含めることはできません。
FixLangの内部では文字列はバイト配列として表現されています。エンコーディングは特に決まっていませんが、アプリケーションではUTF-8として扱うことが多いと思います。
タプル型
(
と )
の間に2個以上の要素を ,
で区切ったものはタプルになります。
タプルの各要素は異なる型であっても問題ありません。
(1, 2) // (I64, I64)型
(3, "Hello") // (I64, String)型
(4.5_F32, 6_U8, [7, 8]) // (F32, U8, Array I64)型
(
と )
の間に1個の要素があるものは、1個の要素をカッコで囲んだものとして解釈されます。
(1) // I64 型
("Hello") // String型
(
と )
の間に1個の要素と,
があるものは、長さ1のタプルとして解釈されます。
(1,) // (I64,) 型
("Hello",) // (String,) 型
ユニット型
(
と )
の間に何も無いものはユニット型になります。ユニット型は()
というただ一つの値しか持ちません。
() // ()型
IO a 型
画面入出力やファイル入出力などのI/O処理は IO a
型になります。
ただし a
はI/O処理結果の型です。
画面出力のように処理結果がとくにない場合は IO ()
型になります。
Main
モジュールのmain
関数はIO ()
型にする必要があります。
input_line // IO String 型
println("Hello") // IO () 型
関数型
関数型は 引数の型 -> 戻り値の型
のように表します。
下記の例は、I64 -> String
という型を持つ関数定義です。(関数定義については、次回の記事で詳しく説明する予定です)
make_item: I64 -> String;
make_item = |id| (
"item-" + id.to_string
);
また、2個の引数を持つ関数の型は 1番目の引数の型 -> 2番目の引数の型 -> 戻り値の型
のように表します。
下記の例は、String -> I64 -> String
という型を持つ関数定義です。
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の基本的な文法について説明する予定です。