関数
今日の多くの言語と同様、Wrenの関数は小さなコードのまとまりで、変数に保存したり、メソッドの引数として渡したりできます。
関数とメソッドには違いがある点に注意してください。
Wrenはオブジェクト指向なので、コードの大半はクラスのメソッドに置きますが、独立した関数も非常に便利です。
Wrenのほかのものと同じように、関数もオブジェクトで、Fnクラスのインスタンスです。
関数の作成 #
関数を作るには、実行するブロックを受け取るFn.newを呼び出します。関数を呼び出すには、その関数インスタンスの.call()を使います。
var sayHello = Fn.new { System.print("hello") }
sayHello.call() //> hello
後で、関数を作成するための短い構文も説明します。
関数のパラメーター #
もちろん、値を渡せなければ、関数はあまり役に立ちません。上の関数は引数を受け取りません。引数を受け取るようにするには、本体の開始の波括弧の直後に、|で囲んだパラメーターリストを置きます。
関数へ引数を渡すには、callメソッドに渡します。
var sayMessage = Fn.new {|recipient, message|
System.print("message for %(recipient): %(message)")
}
sayMessage.call("Bob", "Good day!")
パラメーターリストが要求する個数より少ない引数で関数を呼び出すと、エラーになります。引数が多すぎる場合、余分な引数は無視します。
値を返す #
関数の本体はブロックです。一つの式だけであれば、より正確には{またはパラメーターリストの後に改行がなければ、関数は暗黙にその式の値を返します。
そうでない場合、本体は既定でnullを返します。return文を使うと、値を明示的に返せます。言い換えると、次の二つの関数は同じ動作をします。
Fn.new { "return value" }
Fn.new {
return "return value"
}
callを使うと、その戻り値を受け取れます。
var fn = Fn.new { "some value" }
var result = fn.call()
System.print(result) //> some value
クロージャー #
予想どおり、関数はクロージャーであり、自分のスコープの外で定義した変数にアクセスできます。関数を定義したスコープを離れた後も、閉じ込めた変数を保持します。
class Counter {
static create() {
var i = 0
return Fn.new { i = i + 1 }
}
}
ここでは、createメソッドは、二行目で作った関数を返します。その関数は、関数の外で宣言した変数iを参照します。createから関数を返した後でも、iを読み取ったり、代入したりできます。
var counter = Counter.create()
System.print(counter.call()) //> 1
System.print(counter.call()) //> 2
System.print(counter.call()) //> 3
呼び出し可能なクラス #
Fnはクラスであり、call()に応答するので、どのクラスでもcall()に応答することで関数の代わりに使えます。コールバックやイベントのように、呼び出すために関数をメソッドへ渡す場面で、特に便利です。
class Callable {
construct new() {}
call(name, version) {
System.print("called %(name) with version %(version)")
}
}
var fn = Callable.new()
fn.call("wren", "0.4.0")
ブロック引数 #
呼び出すために関数をメソッドへ渡すのは、非常によくある操作です。Wrenには数え切れないほどの例があります。例えば、関数を受け取るwhereメソッドを使って、リストを絞り込めます。
var list = [1, 2, 3, 4, 5]
var filtered = list.where(Fn.new {|value| value > 3 })
System.print(filtered.toList) //> [4, 5]
この構文は少し読み書きしづらいため、Wrenにはブロック引数という概念があります。関数をメソッドへ渡す際、それが最後の引数であれば、ブロック部分だけという短い構文を使えます。
list.whereでブロック引数を使ってみましょう。これは最後の、そして唯一の引数です。
var list = [1, 2, 3, 4, 5]
var filtered = list.where {|value| value > 3 }
System.print(filtered.toList) //> [4, 5]
前のページでは、mapとwhereを使う同じ構文をすでに見ました。
numbers.map {|n| n * 2 }.where {|n| n < 100 }
ブロック引数の例 #
全体の例を見て、渡す側と受け取る側の両方を確認しましょう。
次は、クリックイベントが送られてきたときに関数を呼び出すものを表す、架空のクラスです。関数だけを渡してマウスの左ボタンを想定することも、ボタンと関数を渡すこともできます。
class Clickable {
construct new() {
_fn = null
_button = 0
}
onClick(fn) {
_fn = fn
}
onClick(button, fn) {
_button = button
_fn = fn
}
fireEvent(button) {
if(_fn && button == _button) {
_fn.call(button)
}
}
}
クリック可能なクラスができたので、使ってみましょう。既定の左ボタンでよいので、まずは関数だけを受け取るメソッドを使います。
var link = Clickable.new()
link.onClick {|button|
System.print("I was clicked by button %(button)")
}
// send a left mouse click
// normally this would happen from elsewhere
link.fireEvent(0) //> I was clicked by button 0
今度は、ボタンの引数を追加して試しましょう。
var contextMenu = Clickable.new()
contextMenu.onClick(1) {|button|
System.print("I was right-clicked")
}
link.fireEvent(0) //> (nothing happened)
link.fireEvent(1) //> I was right-clicked
ほかの引数は通常どおり渡しており、特別なのは最後の引数だけという点に注意してください。
普通の関数にすぎない
ブロック引数は、関数を作成して渡す操作を一つの小さな構文にまとめた、純粋な糖衣構文です。次の二つは同じです。
onClick(Fn.new { System.print("clicked") })
onClick { System.print("clicked") }
次の書き方も、同じように有効です。
var onEvent = Fn.new {|button|
System.print("clicked by button %(button)")
}
onClick(onEvent)
onClick(1, onEvent)
Fn.new
ここまででお気付きかもしれませんが、FnはFn.newのためのブロック引数を受け取ります。このコンストラクターが行うのは、その引数をそのまま返すことだけです!
クラス →← 変数