関数 とは、アクション (Dance()
や Sleep()
など) を実行し、指定した入力に基づいて異なる出力を生み出す、再利用可能なコードです。
関数には、動作に対する 抽象化 があり、つまりこれらの再利用可能な関数は、コードの他の部分に関係しない、確認が必要ない実装の詳細が隠されます。
関数と抽象化の例として、メニューから料理を注文することを考えます。食品注文の関数は、次のようになります。
OrderFood(MenuItem : string) : food = {...}
レストランで料理を注文するとき、OrderFood("Ramen")
で、メニューから食べたい料理をウェイターに伝えます。レストランが料理をどのように調理するのかはわかりませんが、注文後に food
を受け取ることを期待します。他の顧客はメニューから異なる料理を注文でき、その料理を受け取ることを期待します。
つまりこれらの命令は 1 か所で定義するだけでよく、これが関数が便利である理由です。この場合、食品を注文したときに、起きることを定義します。たとえば、レストランのすべての顧客が料理メニューから注文するこの関数を、異なる状況で再利用できます。
下のセクションでは、関数の作成 方法、定義した後の 関数の使用 方法について説明します。
関数を定義する
関数シグネチャ で 宣言 するのは、関数名 (識別子) および関数の 入力 (パラメータ) と 出力 (結果) です。
Verse の関数には、関数を使用または実装する方法を指定する 指定子 もあります。
関数 本体 は、関数が呼び出されるときに実行する内容を コードブロック で定義します。
下のセクションで、これらの考え方を詳しく説明します。
関数の 構文 は次のようになります。
Identifier(parameter1 : type, parameter2 : type) <specifier> : type = {}

パラメータ
パラメータ は入力 変数 で 関数シグネチャ で宣言し、この関数のボディで使用します。関数を呼び出す とき、存在する場合は、値 をパラメータに割り当てる必要があります。代入した値は関数の 引数 と呼ばれます。
Sleep()
など関数にパラメータがない場合もあり、あるいは必要なパラメータ数を指定できます。関数シグネチャでパラメータを宣言するには、丸かっこ ()
の間に識別子と 型 を指定します。複数のパラメータがある場合、コンマ ,
で区切る必要があります。
その例を以下に示します。
Example(Parameter1 : int, Parameter2 : string) : string = {}
次のすべてが有効です。
Foo():void = {}
Bar(X:int):int = X
Baz(X:int, ?Y:int, ?Z:int = 0) = X + Y + Z
構文 ?Y:int
は、名前付き引数を名前 Y
と型 int
で定義します。
構文 ?Z:int = 0
は、名前付き引数を名前 Z
と型 int
で定義します。これは関数が呼び出されるときに指定する必要はありませんが、指定されていない場合に値として 0
を使用します。
結果
結果 は、関数が呼び出されたときの関数の出力です。戻り 型は、関数を正常に実行した場合に、関数から期待できる値の型を指定します。
結果が必要のない関数の場合、戻り型を void
に設定することができます。戻り型に void
が指定されている関数は、常に値 false
を返します。関数ボディに結果の式を指定した場合でもそうです。
指定子
定義された関数の動作を記述する関数の 指定子 に加えて、関数の識別子 (名前) で指定子になります。その例を以下に示します。
Foo<public>(X:int)<decides>:int = X > 0
この例では、Foo
という名前で、パブリックにアクセスでき、decides
エフェクトがある関数を定義します。パラメータ リストの後で、戻り型の前にある指定子では、関数の セマンティクス を記述し、結果の関数の型を示します。関数の名前における指定子は、定義された関数の名前に関係する動作 (可視性など) のみを示します。
関数ボディ
関数ボディ は コードブロック で、関数が実行する内容を定義します。関数は、結果を生み出すために関数ボディの関数シグネチャで定義した、任意のパラメータを使用します。
関数は、最後に実行された式で生み出された値を自動的に返します。
その例を以下に示します。
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2
関数 Bar()
は、Foo[]
が失敗したかどうかに応じて、1
または 2
を返します。
特定の値を強制的に返す (そして関数を直ちに終了する) には、return
式を使用します。
その例を以下に示します。
Find(X:[]int, F(:int)<decides>:void)<decides>:int =
for (Y:X, F(Y)):
return Y
false?
式 return Y
は関数 Find
を終了し、Y
に含まれる値を関数の呼び出し元に返します。false?
は関数を強制的に失敗させる方法として使用されることに注意してください。この場合、これは意味があります。述語 F
に一致する値が X
に見つからなかったからです。void
を返すように定義された関数の場合は、式を return
式で指定する必要がありません。
その例を以下に示します。
AnyOf(X:[]int, F(:int)<decides>:void)<decides>:void =
for (Y:X, F(Y)):
return
false?
エフェクト
関数の エフェクト には、関数が呼び出されたときに、選択できる追加の動作を記述します。具体的には、関数の decides
エフェクトでは、呼び出し元が必要とする 処理 (decides
としてマークすることで、呼び出し元に伝播する) 方法で失敗できることを示します。
たとえば、次のような場合です。
Fail()<decides>:void = false?
これは常に失敗する関数を定義します。どの呼び出し元も失敗を処理、伝える必要があります。構文の注意事項:エフェクトは関数で指定子として記述します。このようなエフェクトがある関数の型は、タイプ マクロにより関数の定義を非常に似たものにすることができます。
type{_()<decides>void}
関数を呼び出す
関数呼び出しは 関数を評価 (コールまたは呼び出し) する 式 です。
Verse の関数呼び出しには 2 種類の形式があります。
-
FunctionName(Arguments)
:この形式では、関数呼び出しが成功し、どのコンテキストでも使用できることが必要です。 -
FunctionName[Arguments]
:この形式では、関数呼び出しが失敗する可能性があります。この形式を使用するには、関数は、属性<decides>
を指定して定義し、失敗コンテキストで呼び出す必要があります。
関数に decides
エフェクトがない場合に、丸かっこを使用することで、呼び出しが実行されます。その例を以下に示します。
Foo()
Bar(1)
Baz(1, ?Y := 2)
Baz(3, ?Y := 4, ?Z := 5)
Baz(6, ?Z := 7, ?Y := 8)
名前付き引数 (たとえば ?Y:int
) が ?
に先頭が付いた名前を参照し、そして :=
の右に値を指定することで、渡される方法に注意してください。名前付き引数 ?Z
が任意であることにも注意してください。重要なのは、呼び出しサイトでの名前付き引数の順序は、関係ありませんが、名前付き引数で値を生み出す間にサイド エフェクトが発生する可能性があります。
decides
エフェクトがある関数を 呼び出す には、角かっこを使用する必要があります。これにより、decides
エフェクトを受ける配列のインデックス処理で、decides
エフェクトでマークした関数と同様の構文に従うことができます。その例を以下に示します。
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2
タプルをアンパックする
複数の引数を受け付ける関数は、複数引数と同じ型の要素がある、単一の タプル 引数を受け付ける関数を呼び出したときに区別できません。呼び出したときに区別できないと、各関数の型にも適用されます。双方とも同じ型になります。
その例を以下に示します。
Second(:any, X:t where t:type):t = X
これは以下と同じです。
Second(X:tuple(any, t) where t:type):t = X(1)
両方とも次のとおり呼び出しできます。
X := 1
Y := 2
Second(X, Y)
または
X:tuple(int, int) = (1, 2)
Second(X)
両方とも型 type{_(:any, :t where t:type):t}
を満たします。
関数型
関数の型は、パラメータの型 (潜在的に、アンパックされたタプルとして定義)、そのエフェクト、その結果の型で構成されます。その例を以下に示します。
type{_(:type1, :type2)<effect1>:type3}
これは 2 個の引数 type1
と type2
を取る関数の型 (tuple(type1, type2)
型の引数と同等) で、エフェクト effect1
を生み出し、type3
型の値を返します。
オーバーロード
複数の関数で、引数によりそれらの関数を区別できる場合に、同じ名前を共有できます。これは オーバーロード として知られています。
その例を以下に示します。
Next(X:int):int = X + 1
Next(X:float):float = X + 1
int_list := class:
Head:int
Tail:?int_list = false
Next(X:int_list)<decides>:int_list = X.Tail?
これらの関数が受け付ける引数に重なりがありません。呼び出される関数が、指定された型により、明確に正しく解決できます。その例を以下に示します。
Next(0)
Next(0.0)
Next(int_list{Head := 0, Tail := int_list{Head := 1}})
ただし、次の場合は許可されません。
First(X:int, :any):int = X
First(X:[]int)<decides>int = X[0]
この理由は、タプルと配列にサブタイプの関係、つまり配列の基本型がタプルの要素型ですべてのスーパータイプであるとき、配列はタプルのスーパータイプになるからです。その例を以下に示します。
X := (1, 2)
First(X)
この例では、First
へのコールは、First
のいずれの定義でも満足できます。クラスとインターフェースの場合は、オーバーロードは起きません。クラスは後でインターフェースを実装するために変更でき、または 2 つのクラスは継承関係が生まれるように変更できるからです。代わりに、メソッドのオーバーライドを使用できます。たとえば、次のような場合です。
as_int := interface:
AsInt():int
ToInt(X:as_int):int = X.AsInt()
thing1 := class(as_int):
AsInt():int = 1
thing2 := class(as_int):
AsInt():int = 2
Main()<decides>:void =
X := thing1{}
ToInt(X) = 1
Y := thing2{}
ToInt(Y) = 2
クラス 定義の一部である関数は メソッド と呼ばれ、追加の機能があります。Verse の関数に続いてメソッドを習得するには、「クラス」を参照してください。