ろじかるんるんものがたり

病人が特に何も書かない。無駄だからだ。

Scala のデフォルト引数の評価タイミングとフリップフラッパーズ

この記事は Adventar の Scala Advent Calendar 2016 の 25 日目の記事です。遅れてすいません。まあ登録したのが 25 日だったと思うので許して欲しい。あと忙しかったり体調崩したりしてた。

カレンダーの占めなのでドカンと何か書くべきだし、書きたいことも色々あるのですが、Scala にフォーカスして、となるとぱっと書けるものはないので(ぱっと書けないものはあるけど今は時間がない)、適当にクイズでも。Scala パズルです。

www.shoeisha.co.jp


というわけで早速問題。以下のコードを実行すると、何が出力されるでしょう。

def f(x: Int = { println("flip flap"); 0 }) = x

f()
f()

特に難しくはないです。いきなり正解というのもなんですし、今期の面白かったアニメイションの宣伝でもします。

今期はなんといってもフリップフラッパーズでしょう。すっかり原作付きアニメが増えた昨今ですがオリジナルアニメです(今期はオリジナル多かった気がする)。シナリオ構成が綾奈ゆにこさんだったので一応チェックはしていたんですが、完全にブラックホースでした。ちょうど今日、一部の地上波で最終話が放送されます。

www.flipflappers.com

この記事を見ている頃にはもう放送は終わっているでしょうが…良かったですね!ウェッブで、正確には abema TV で明日最終話見ることができます。なんと最終話放送前に、1 話から 12 話までの一挙放送もあるので、まだ見ていないという方にも優しい構成です。一挙放送の後に WEB での最速配信で最終話という完璧な構成。やったぜ。

abema.tv

話としては、所謂 Girl meets Girl な SF です。
女の子が突然現れた女の子に振り回されて冒険譚します。六話までは深く考えずに見ましょう。毎話毎話、テーマの違う異世界に行って冒険譚をするのが基本的な流れです。うさぎ回(?)あり、サバイバル回あり、ライバル登場回あり、ホラー回あり、シリアス回あり…たまに「あれ話とんだ?」みたいな感じになりますが、後半回収されるので気にしなくていいです。後半、箸休め的にロボ回もあります。一応ある登場人物の重要なターニングポイント回でもありますが。
繰り返しになりますが六話までは、兎に角深く考えずに見ればいいです。それだけで楽しめます。一話完結物見てるつもりで見てればいいです。良く動くし背景も劇伴も演出も凝ってるし、勿論お話も毎話良く出来ています。六話なんて違うアニメをみてたのかな?という感じになると思います。
七話から徐々に風呂敷を畳んでいく流れになります。これを書いている時点で最終回を見られていないので、どういう結末を迎えるのか分かりませんが、とりあえず 12 話までは毎話面白く見ています。

兎に角視聴しましょう。というわけで、そろそろ答え合わせです。出力結果は…

flip flap
flip flap

flip flap flip flap。簡単すぎましたかね。エンディングのサビの歌詞の一部です。もうフリップフラッパーズの話はいい?そんなあ…

デフォルト引数は、メソッド呼び出しのタイミングで毎回評価されます。そもそもデフォルト引数に副作用を伴うような値を置くなという話はありますが、ちょっとしたクイズでした。詳細は SLS 6.6.1 参照。

なんでこんなクイズを出したかというと、Python がデフォルト引数を関数定義時に評価して使いまわすことをふと思い出したからでした。ろくに書いたこともない言語の pitfalls なんてなんで知ってるのか、なんで思い出したのか全く意味が分かりませんが、人間なんてそんなものです。まるで一貫性がない。記憶は揮発性。ろくなもんじゃない。

プログラミングする上で「いつ何が評価されるか」を正しく知っておくことは重要です。例えば、有名な話ですが by-name parameter は call-by-need ではなく call-by-name に評価されます。

def twice(f: => Unit) = { f; f }
twice { println("flip flap") } // "flip flap\nflip flap\n" が出力されます

あとは、無名関数だと思って書いた部分が、実は無名関数内で評価されずに、先に評価されちゃっていた、とか。よくあるやつです。akka-http のドキュメントに丁度よい例がありますね。

val a = {
  println("MARK")
  complete("yeah")
}

val b = complete {
  println("MARK")
  "yeah"
}

`{ }` は無名関数とは関係なく、単なるブロック式だというのは Scala 初心者、初学者のはまりがちなポイントの一つだと思います。`{ case => ... }` のようなブロックが Function ないし PartialFunction が期待される文脈で記述されているとよしなに変換されるとかいう、なんでそれ文法レベルで無名関数にしなかったんだよという無駄に複雑なルールもありますしね。誤解しやすい。

他に評価タイミングネタだと shapeless の Lazy トレイトとかあるんですが、こいつはただの call-by-need な semantics を表すものかと思いきや、implicit parameter の解決に絡む闇があり、これだけで記事一つ書ける奴なので…あれ、じゃあそれについて書けばよかったんでは???

ということで、そろそろ TOKYO MX で放送が始まってしまうのでおしまいです。
Scala Advent Calendar にかこつけてフリップフラッパーズ宣伝したかっただけです、すいません。

今年中に記事書くのはこれが最後かな。良いお年を。来年頭は 2016 年に読んだ面白論文紹介記事とかになると思います。