Scala で for 式の中括弧{} 内で <- expression が受け取れる型について
どういう経緯だったか忘れてしまったのだけれど
Haskell の do 記法は Monad 型クラスの >>= (bind?) 等に変換される糖衣構文で、do 記法で使えるのは Monad 型クラスのインスタンスを持つ型だけだけど、Scala の for 式はどういう形で受け取る型を決めているんだろう?
という話になり、「Traversable
に flatMap
とか定義されているから Traversable
が受け入れられるのでは?」と素朴に思ったのですが、Option
とか Future
とか明らかに違うやつがいくらでもいるので調べました。
※さらにいうと、Scala 仕様書の for
のところ見ても糖衣構文だよーとかどう分解するかということまでは書いてあるものの受け入れられる型みたいな話はなかったり、"Scala for 受け入れられる型" みたいなググり方をしてもなかなかたどり着けなかったので、私みたいな検索弱者用に書く
公式ドキュメントにめっちゃ書いてあった
Scala’s “for comprehensions” are syntactic sugar for composition of multiple operations with foreach, map, flatMap, filter or withFilter. Scala actually translates a for-expression into calls to those methods, so any class providing them, or a subset of them, can be used with for comprehensions.
ただし、How does yield work? という yield
に特化したタイトルで、 Scala FAQs って謎階層にいたので、みつからないよ...って思った。(Scala の Gitter チャンネルで Tsuyoshi Yoshizawa さんに教えていただいた)
ためした
object Main extends App { val forPo = new Forable("Po") val po = for { u <- forPo } yield u println(po) // Kaboom! Cannot compile - lack of flatMap // val popo = for { // u <- forPo // v <- forPo // } yield u + v val multiForPo = new MultiForable("Po") val multiForPoPo = new MultiForable("PoPo") val popopo = for { po <- multiForPo popo <- multiForPoPo } yield po + popo println(popopo) // Kaboom! Cannot compile - lack of foreach // for { // po <- multiForPo // popo <- multiForPoPo // } println(po + popo) val sideEffectPo = new SideEffectForable("Po") val sideEffectPoPo = new SideEffectForable("PoPo") for { po <- sideEffectPo popo <- sideEffectPoPo } println(po + popo) // Kaboom! Cannot compile - lack of filter // val popo = for { // popo <- multiForPoPo if multiForPoPo.x == "Po" // } yield popo val filterPoPo = new FilterForable("PoPo") val popo = for { popo <- filterPoPo if filterPoPo.x == "PoPo" } yield popo println(popo) } class Forable[A](val x: A) { def map[B](f: A => B): Forable[B] = new Forable(f(x)) override def toString(): String = x.toString } class MultiForable[A](x: A) extends Forable(x) { def flatMap[B](f: A => Forable[B]): Forable[B] = f(x) } class SideEffectForable[A](val x: A) { def foreach(f: A => Unit): Unit = f(x) } class FilterForable[A](x: A) extends MultiForable(x) { def withFilter(expr: A => Boolean): FilterForable[A] = this // easy implementation }
ダラダラと書いたけど、つまるところ map
とかのシグネチャを満たしてれば普通に for comprehension
で <-
の右側に使えることがわかった。
Scala の for 式は実は単に flatMap
等のへの糖衣構文だったり明らかに Haskell の do
記法 をパクった に近いにも関わらず、Haskell の do
記法が Monad
型クラスを要求するのとは結構毛色が違う感じがする。
受け入れられるシグネチャについても結構ゆるくて
class Forable[A](val x: A) { def map(f: A => A): MultiForable[A] = new MultiForable(f(x)) }
とか、かなり原型をとどめていなくても行けたので、シグネチャというより、本当にメソッド名さえあっていれば良くて、脱糖されてから型検査が通ればオッケー(実際脱糖は型検査以前に行われるらしい)、という感じのようだ。
結構ふわっとした仕様でちょっと意外だった。
おしまい。