- 無名関数と
returnで小話を一席
- Twitter: dico_leque
- 趣味 Schemer
- お仕事で Scala とか OCaml とか F# とか Java とか
public <T> boolean contains(T x, T[] arr) {
for (T elem : arr) {
if (elem.equals(x))
return true;
}
return false;
}- 無名関数を使って同じように書こうとするとどうなる?
- JSR 335: Lambda Expressions for the JavaTM Programming Language の話は最後に
def contains[T](x : T, ys : List[T]) : Boolean = {
ys.foreach { y =>
if (y == x)
return true
}
return false
}-
{ arg => body }で無名関数。無名関数はbodyの部分で最後に評価した式の値を返す -
returnはcontainsから返る -
return先のメソッドのアクティブな呼び出しが複数あってもreturn先は字面で決まる。レキシカルスコープ -
無名関数をただのブロックっぽく使える
-
Java レベルでは……
- 無名関数ひとつごとに
scala.runtime.AbstractFunction1のサブクラスな 無名クラスのインスタンスをひとつ return trueの方は、containsの呼び出しごとにObject kを作り、foreachの内側のreturnはそれをマーカにした例外をthrow。scala.runtime.NonLocalReturnControl exnをcatchしてexn.key() == kならそこでreturn exn.value。
- 無名関数ひとつごとに
-
単純に例外を
throwするだけだと dynamic scope っぽくなる -
return falseの方はただのreturnになる -
return を含む無名関数をコンテキスト外に取り出して実行すると NonLocalReturnControl 例外が見える(ただし型安全なプログラムを書いているかぎり そういうことはできない(と思う))
-
実際に書く場合は
returnは使わず再帰で
def contains[T](x : T, ys : List[T]) : Boolean = {
ys match {
case Nil => false
case y::rest =>
if (y == x)
true
else
contains(x, rest)
}
}Collection>>contains: anObject
self do: [:each| each = anObject ifTrue: [^true]].
^false[:arg| ... ]で無名関数(BlockContext)^exprでメソッドから値を返す。無名関数は最後に評価した式の値を返す- Scala と同じような感じ
- 条件分岐にも無名関数を使う e.g.
Boolean>>ifTrue: - 制御構造はすべてメソッド呼び出し + ブロックで書く
- ブロックを外に持ち出してコンテキスト外で
^するとBlockCannotReturnエラー selfはブロックの外側のselfになる
_AddSlots: (|
contains: anObject = (
do: [|:each.:idx| anObject = each ifTrue: [^true]].
^false
)
|)
- プロトタイプオブジェクト指向言語
- メソッドとブロックは別。メソッド: ( ... )、 ブロック [ ... ]
- メソッドも first-class
^はメソッドから抜ける- あとは Smalltalk と同じ
- プロトタイプオブジェクト指向言語
functionしかない(←→ Self)returnはfunctionから抜ける- 途中脱出するなら
throw(Scala と同じような感じで) - return しないと値を返せないから仕方ない。
- 他にも内側の
functionから外側のthisを参照したつもりでハマる
- そもそも
returnがない throw Exitでほげほげ(ただし複数のExitを区別できない。 dynamic-scope)- ローカルモジュールで毎回例外を定義してそれを投げればどの
returnか区別できる (Janestreet Core https://bitbucket.org/yminsky/ocaml-core/wiki/Home のwith_return) - そもそも
return的なものを使わず再帰で書く
- 大域脱出のような計算効果 (computational effect) を扱いたければモナドで
Errorモナドとか- そもそも
return的なものを使わず再帰で書く
call/ccで手続きの戻り値を待つ継続を捕捉しておいて、returnしたいところで その継続を起動する- 継続はふつうの first-class object なので、
returnしたいところまで 変数に入れて持っていけばよい call/cc(multi-shot continuation) は重いので、 本当はcall/ec(escape continuation, one-shot continuation) を使った方がよい。returnもthrowもただの脱出継続- そもそも
return的なものを使わず再帰で書く
returnでもっとも内側のblockから返るreturn-from name valでreturn先を指定できるreturnは lexical-scope (←→catch,throw)- 関数型とかどうでもいいので
loopからreturnする(個人差があります)
thisとsuperは外側のコンテキストを見るreturnは無名関数からのreturnbreak/continueは non-local jump しない
-
プログラムの意味は lexical (静的)に決まった方がうれしい
-
Scala 等の
return風の挙動を例外で再現するのは少しだけ面倒 -
無名関数をふつうの block のように使いたい場合は
returnは 外に効いてくれた方がうれしい -
JavaScript は
functionとmethodを分けた方がよかったのでは -
でも
return必須な言語ではfunctionからどう返れば…… -
JSR-335 は JavaScript 風の
thisみたいなハマり方はしない -
returnの場所によってコストが変わったりしないのは無難 -
returnもthrowもbreakもcontinueもただの継続の起動