関数の解剖
Zero の関数の一般形は次の通りです。
fun name(param1: Type1, param2: Type2) -> ReturnType {
// 本体
return value
}
各要素は、
fun——関数を導入するキーワード。name——関数の名前。(param1: Type1, ...)——パラメータリスト。各パラメータは明示的な型を持つ。-> ReturnType——戻り値の型。{ ... }——本体、文のブロック。return value——valueで関数を終了する。
具体的な小さい例。
fun double(value: i32) -> i32 {
return value * 2
}
これが関数の全部です。i32 をひとつ取り、別の i32 を返し、I/O はしません。本体の中では value は let スタイルのバインディングで、他のローカルと同じように使えます。
関数を呼ぶ
呼び出しは想像通りの見た目です。
let result = double(21)
引数はパラメータの型と一致する必要があります。結果は result に束縛され、double が i32 を返すのでコンパイラーが i32 と推論します。
ヘルパーと main を組み合わせた動作例——Run をクリックして動きを見てみてください。
標準出力に math works\n が表示されるはずです。
pub と可視性
デフォルトでは、ファイル内で宣言された関数はそのファイル(あるいはモジュール——プロジェクトが大きくなると可視性ルールは厳しくなります)にとってプライベートです。関数をモジュールの外に公開するには pub を前に付けます。
pub fun greet() -> String {
return "hello\n"
}
pub がないと、他のモジュールのコードは greet を呼べません。ランタイムはどのユーザー定義モジュールの外からでも main を呼ぶ必要があるので、main は常に pub です。
デフォルトでプライベートというルールは、活用する価値のあるものです。インターフェイスとして意図したものだけマークし、残りはモジュールの内側に留めましょう。
戻り値の型
すべての関数は -> のあとに戻り値の型を宣言します。よくある戻り値の型は、
fun answer() -> i32 { return 42 }
fun ok() -> bool { return true }
fun label() -> String { return "ready\n" }
fun nothing() -> Void { }
Void は、値を生成するのではなく副作用で仕事をする関数の戻り値の型です。Void 関数には明示的な return は必要ありません——本体の末尾まで到達すれば十分です。
fun log(world: World, message: String) -> Void raises {
check world.out.write(message)
}
戻り値を捨てる関数呼び出し
関数が値を返してもそれが気にならない場合でも、戻り値を認識する必要があります。慣用は let で束縛することです。
ignored は、関数の残りの部分で読まれないバインディングです。ignored(あるいは _)という名前を使う慣習は、その捨てが意図的であることを示します。これは戻り値を黙って捨てるよりも摩擦がありますが、それがポイントです——エージェントが読み書きする言語では、読まれていない値はしばしば表面化する価値のあるバグです。
raises の役割
失敗しうる関数はシグネチャでそれを宣言します。main で見たやつですね。
pub fun main(world: World) -> Void raises {
check world.out.write("hello\n")
}
raises 句は素(任意のエラー)でも特定でも書けます。
fun validate(ok: Bool) -> i32 raises { InvalidInput } {
if ok == false {
raise InvalidInput
}
return 42
}
raises { InvalidInput } は「この関数は InvalidInput で失敗しうる、それ以外では失敗しない」を意味します。呼び出し側は check(あるいはより込み入ったハンドリング構文)でエラーを伝播またはハンドリングしなければなりません。
Raises と Check で、複数のエラー型がある場合や、check が呼び出し側の raises 句とどう相互作用するかを含め、詳しく扱います。
ジェネリック関数
関数を 2 つ以上の型で動かしたい場合は、山括弧で型パラメータを宣言します。
fun makePair<T, U>(left: T, right: U) -> Pair<T, U> {
return Pair { left: left, right: right }
}
T と U は型パラメータで、呼び出し側が何にするかを決めます。makePair(40, 2_u8) を呼ぶと Pair<i32, u8> が得られます。ジェネリックなシェイプや制約を含む完全な話は ジェネリクス を参照してください。
関数が住む場所
小さなプログラムでは、関数を .0 ファイルに直接書きます。パッケージ では、関数を src/ 配下の複数ファイルに分散し、コンパイラーがファイル間の参照を解決します。基本——fun、パラメータ、戻り値の型、本体——は、関数が物理的にどこにあるかに関わらず同じです。
スタイルメモ
公式サンプルで見かけるいくつかの慣習です。
- 小文字の関数名で、単語を続ける(
makePair)か camelCase で区切ります。標準ライブラリは camelCase を好みます。 - 関数ごとに戻り値はひとつ。複数のものを返す必要があるなら、それらのための小さな
shapeを作るのがクリア——タプルのペアのタプルを返すより明確です。 Void関数はcheck呼び出しだけを行い、値を計算する関数は可能なら I/O を避けます。この分離は部分的に文化的で部分的に強制されています——純粋な計算関数はworldを取らないので、文字通り I/O できません。
最後の点は心に留める価値があります。I/O は World ケイパビリティの背後にあり、World は明示的に渡されるので、関数のシグネチャから I/O の可能性を判断できます。シグネチャに World を言及しない関数は、外の世界に対して純粋です。それは本文を読まずにエージェント(や人間)が頼れる性質です。
次回: If/Else
if はあちこちで顔を出していました——次のドキュメントでは if/else 式 を詳しく扱います。バインディングとの相互作用、そして意図的に欠けているもの(暗黙の真偽値解釈なし、三項演算子なし)も含めて。
よくある質問
Zero で関数を宣言するには?
fun を使います。fun 名前(パラメータ: 型) -> 戻り値の型 { 本体 } の形です。モジュールの外から見えるようにするには前に pub を付けます。失敗しうる関数なら戻り値の型のあとに raises を付けます。例: pub fun double(value: i32) -> i32 { return value * 2 }。
pub キーワードは何をしますか?
pub キーワードは何をしますか?pub は宣言を public——現在のモジュールの外のコードから見えるようにマークします。pub がないと、関数は宣言されたファイル(あるいはパッケージ)にとってプライベートです。慣用的なエントリーポイント pub fun main はランタイムが見つけて呼べるように public でなければなりません。
Zero で関数から値を返すには?
関数本体内で return 値 と書きます。式は宣言された戻り値の型と一致する必要があります。戻り値の型が Void の関数は何も返さず、明示的な return 文も不要です——本体の末尾まで到達すれば十分です。
Zero の関数は複数のパラメータを取れますか?
はい。それぞれの名前と型をカンマで区切って括弧内に並べます。fun add(a: i32, b: i32) -> i32 { return a + b }。各パラメータは関数本体内で let スタイルのバインディングになります。Zero は関数宣言でのパラメータ型を明示することを要求します——パラメータの型推論はありません。
関数シグネチャの raises の意味は?
raises の意味は?raises は、関数が失敗しうることを宣言します。素の raises は任意のエラー型を許容します。raises { InvalidInput } は特定の名前付きエラーに制限します。呼び出し側は check(あるいは別の失敗ハンドリング構文)を使って失敗の可能性を認識しなければなりません——黙って無視することはできません。