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

どこでも Markdown Table Editor

まずは Atom 用のパッケージのアップデートのご報告です.

atom.io

ファイル内の全ての表をフォーマット, 保存時にフォーマットなどの機能が増えました.

こちらの映像は Take 4 です.

f:id:susisu:20171124004122g:plain

閑話休題.

上のパッケージのせいで, Atom 以外のエディタや, たとえばブログの編集画面とかでもですが, Markdown を書いていて表を書く必要に迫られた際に非常に強いストレスを感じるようになってしまいました. また, 何らかの事情で Atom を使い続けるメリットが少なくなっても, そのせいで Atom から移れない, なんてことになっては困ります.

というわけで AtomAPI から切り離し, テキストエディタに対する共通のインターフェースさえ用意すれば (JavaScript が動きさえすればですが) どこでも使えるようにしました. 切り離し, と言っても実際はフルスクラッチ.

github.com

デモページはこちらです. そのソースコードはこっち. これは CodeMirror を使ってブラウザ上で試せるようにしたものです. 実装すべき機能は概ねどのテキストエディタにも標準で用意されているような基本的なものになっているので, CodeMirror のドキュメントを読みながら数時間程度で実装できました.

皆さまのテキストエディタ, ブラウザ上で動作する Markdown エディタにもおひとつどうですか?