Scala の for ... yield の話

みなさんメリークリスマしておめでとうございます. 2019年もよろしくお願いします.

以下は気まぐれに Scala を書いていたら for ... yieldHaskelldo の微妙な違いでつらみが出てきたときのメモです. Scala 素人もいいとこなのでもっと良い書き方があったら是非教えてください.


例として getNumbers(t: T): Either[String, List[Double]] が何か知らないけれど存在するとして, その結果の平均を求めたいとする. もし不都合があればエラーメッセージを Left にくるんで返す.

Haskell で書くとこう.

do
  xs <- getNumbers t
  case xs of
    [] -> Left "no data"
    _  -> Right $ sum xs / fromIntegral (length xs)

Scala ではこうなるはず.

for {
  xs <- getNumbers(t);
  ret <- xs match {
    case Nil => Left("no data")
    case _   => Right(xs.sum / xs.length)
  } 
} yield ret

なんか余計な変数が生えて気持ちが悪い. (注: この場合は ret のかわりに avg と変数名をつければ違和感は減るが, 一般に適切な名前を付けられるかというとそんなことはないのであえてこうしている.) あと気になるのはインデントが最後の行で先頭に戻た上で何か (yield ...) が書いてあることで, 後続の処理があるのかなとパッと見で勘違いしてしまいそう.

一つの解決策として, ここではパターンマッチを使っているけど, もし適当な述語 (isEmpty とか) があるなら when[A](cond: Boolean)(conseq: => Either[A, Unit]): Either[A, Unit] みたいなのを使ってアーリーリターン的に書けば良い. (Scalaz とかに whenM がある.) でももっと特殊な場合とか自前のデータ構造の場合とかは別に述語を定義する必要があって面倒っぽい.

for {
  xs <- getNumbers;
  _ <- when(xs.isEmpty) { Left("no data") }
} yield xs.sum / xs.length

希望としては例えばこんな風に書けるとうれしいかも.

for {
  xs <- getNumbers(t); 
} yield <- xs match {
  case Nil => Left("no data")
  case _   => Right(xs.sum / xs.length)
} 

続きまして, getNumbers に与える引数が長くて (あるいは後で使うので) 別の変数に代入しておいた方が良い場合.

まずは Haskell の場合. (もう平均は面倒なので単に和を求めることにする.)

do
  let t = supercalifragilisticexpialidocious
  xs <- getNumbers t
  Right $ sum xs

続いて Scala.

for {
  t = supercalifragilisticexpialidocious;
  xs <- getNumbers(t)
} yield xs.sum

と書きたいところだけど, これは駄目で,

{
  val t = supercalifragilisticexpialidocious
  for {
    xs <- getNumbers(t)
  } yield xs.sum
}

としないといけない. なんか不必要に { ... } のネストが増えていてつらい.

ちなみに変数定義は先頭でなければできるので, こういうのは書ける.

for {
  xs <- getNumbers(t);
  s = xs.sum
} yield s