関数とは、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 は、関数呼び出し時に指定が必須ではない型 int の名前付き引数 Z を定義します。ただし、指定されない場合、その値として 0 を使用します。
結果
結果 は、関数が呼び出されたときの関数の出力です。 return 型は、関数を正常に実行した場合に、関数から期待できる値の型を指定します。
関数に結果を持たせたくない場合は、return 値を void に設定できます。 関数本体に結果の式を指定した場合でも、return 型に void が指定されている関数は、常に値 false を返します。
指定子
定義された関数の動作を記述する関数の 指定子 に加えて、関数の識別子 (名前) で指定子になります。 例:
Foo<public>(X:int)<decides>:int = X > 0この例では、Foo という名前で、パブリックにアクセスでき、decides エフェクトがある関数を定義します。 パラメータ リストの後で、return 型の前にある指定子は、関数のセマンティクスを記述し、結果の関数の型を示します。 関数の名前における指定子は、定義された関数の名前に関係する動作 (可視性など) のみを示します。
関数ボディ
関数本体は、関数が何を行うかを定義するコード ブロックです。 関数は、結果を生み出すために関数ボディの関数シグネチャで定義した、任意のパラメータを使用します。
関数は、最後に実行された式で生み出された値を自動的に返します。
例:
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2関数 Bar() は、Foo[] が失敗したかどうかに応じて、1 または 2 を返します。
特定の値を強制的に返す (そして関数を直ちに終了する) には、return 式を使用します。
例:
Minimum(X:int, Y:int):int =
if (X < Y):
return X
return Yreturn X 式は Minimum 関数を終了し、X に含まれる値を関数の呼び出し元に返します。 なお、ここで明示的な return 式を使用しない場合、この関数はデフォルトで最後に実行された式を返します。 これにより、常に Y の値が返されることになり、誤った結果になる可能性があります。
効果
関数に対するエフェクトは、関数が呼び出されたときに実行できる追加の動作を記述します。 具体的には、関数の decides エフェクトは、呼び出し元が処理する必要がある方法で関数が失敗する可能性があることを示します (または、decidesとしてマークすることで、呼び出し元に伝播します)。
例:
Fail()<decides>:void = false?これは常に失敗する関数を定義します。 どの呼び出し元も失敗を処理、伝える必要があります。 構文に注意してください。エフェクトは関数で指定子として記述します。 このようなエフェクトがある関数の型は、タイプ マクロにより関数の定義を非常に似たものにすることができます。
type{_()<decides>void}decides エフェクトを持つ関数は、関数が成功した場合に値を返すこともできます。
例:
First(Array:[]t, F(:t)<transacts><decides>:void where t:type)<transacts><decides>:t =
var ReturnOption:?t = false
for (Element : Array, F[Element], not ReturnOption?):
set ReturnOption = option{Element}
ReturnOption?この関数は、指定された decides 関数の実行が成功した結果となる最初の配列値を決定します。 失敗コンテキスト内からの明示的な return 文は許可されていないため、この関数は option 型を使用して return 値を保持し、関数が成功するか失敗するかを決定します。
失敗と for 式を組み合わせることもできます。 for 式内に失敗式がある decides 関数は、for 式のすべてのイテレーションが成功した場合にのみ成功します。
例:
All(Array:[]t, F(:t)<transacts><decides>:void where t:type)<transacts><decides>:void =
for (Element : Array):
F[Element]この関数は、配列のすべての要素の結果が関数 F が成功した場合にのみ成功します。 配列からの入力のいずれかが関数 F で失敗した場合、全て 関数は失敗します。
for 式で失敗を結合して、成功または失敗になる入力に基づいて入力をフィルターすることもできます。
例:
Filter(Array:[]t, F(:t)<transacts><decides>:void where t:type)<transacts>:[]t =
for (Element : Array, F[Element]):
Elementこの関数は、F 関数の結果として配列から要素のみを含む配列を返します。
関数を呼び出す
関数呼び出しは、関数を評価 (呼び出しまたは起動とも呼ばれます) する式です。
Verse の関数呼び出しには 2 種類の形式があります。
FunctionName(引数): この形式では、関数呼び出しが成功し、どのコンテキストでも使用できることが必要です。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