みなさんメリークリスマしておめでとうございます. 2019年もよろしくお願いします.
以下は気まぐれに Scala を書いていたら for ... yield
と Haskell の do
の微妙な違いでつらみが出てきたときのメモです.
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