ScalaでSchemeのnamed letもどきを作る

いきなりですが、皆様は、Schemeのnamed let(名前付きlet)はご存知でしょうか。これはどういうものかというと非常に簡単な話で、単に、その場で関数を定義して呼び出すだけのものです。Schemeで実際にコードを書くと以下のようになります。

(display (let fact ((n 10)) (if (< n 1) 1 (* n (fact (- n 1))))))

このコードは、10の階乗を計算する関数factを定義して、その場で10を引数として呼び出し、その結果を出力しています(3628800になります)。

さて、上記のSchemeのコードの意味はともかくとして、Scalaでも、その場で(再帰可能な)名前付き関数を作ってその場で呼び出したいときがあります。通常の場合は、一度defで明示的に関数を定義してあげなければいけませんが、次のようなコードでSchemeのnamed letをエミュレートできます。

def let[A, B](arg :A)(f :(A => B, A) => B) :B = { def fix(f:(A => B, A) => B)(x1 :A) :B = { f({x2:A => fix(f)(x2)}, x1) } fix(f)(arg) }

とりあえず、このコードの意味はおいといて、実際に使ってみましょう。

println(let[Int, Int](10){(fact, n) => if(n < 1) 1 else n * fact(n - 1) })

このコードを実行すると、Schemeの例と同様に3628800が表示されます。型パラメタを明示しなければいけないのが、ちょっとイケてませんが、まあ普通のScalaの関数でも引数の型を明示しなければいけないことを思えば大した事は無いでしょう。と、話がそれましたが、記法こそ違うものの、このようにしてScalaでもSchemeのnamed letをエミュレートすることができました。

注意しなければならないのは、このnamed letもどきを使った場合、たとえ末尾再帰であっても深い再帰をするとスタックオーバーフローしてしまう、という点です。とはいえ、このようなコードがどうしても必要な場面は限られるでしょうから、この制限はそれほど深刻なものではないでしょう。