- 無名関数と
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
は無名関数からのreturn
break
/continue
は non-local jump しない
-
プログラムの意味は lexical (静的)に決まった方がうれしい
-
Scala 等の
return
風の挙動を例外で再現するのは少しだけ面倒 -
無名関数をふつうの block のように使いたい場合は
return
は 外に効いてくれた方がうれしい -
JavaScript は
function
とmethod
を分けた方がよかったのでは -
でも
return
必須な言語ではfunction
からどう返れば…… -
JSR-335 は JavaScript 風の
this
みたいなハマり方はしない -
return
の場所によってコストが変わったりしないのは無難 -
return
もthrow
もbreak
もcontinue
もただの継続の起動