独学大学情報学部

ただのノート。主にプログラミング。

Scala秋祭り2019で「Scala における継続モナドの実装と活用」って内容で発表してきた

こんなイベントがあったとさ。

scala-aki-matsuri.connpass.com

資料はこれ。

speakerdeck.com

そういえば、Scala関西Summit 2019 でも登壇するのでよしなに。

Scala関西Summit 2019

f:id:Aoino:20190918044738p:plain
Scala関西Summit 2019 タイムテーブル

以下中身のない話。

やっぱりスライドは Markdown で書けると楽だよねって長年思ってたんですが、最近はレイアウトだの画像貼り付けだのが面倒になったので、 Google Slides に Gist からソースコード貼りまくる感じになった。見た目がいい感じにはできたのでひとまず満足してるんですが、

このあたりはなんとかいい感じになってほしい気がする。 自分でツッコミ入れるのあれですけど、二つ目は普通にバグでは...? (とはいえ約70枚のスライドをハイライトのタグが入ったソースコードで埋め尽くすような利用者が果たしてどのくらいいるのか...

なんかここ最近全然発表も OSS 活動(?)とかブログ書いたりとかしてなかったし、そういう活動を積極的にやりたい気力も色々あってなくなってたんですが、 いざ久しぶりに話をしてみるとやっぱり楽しいので、ぼちぼちいい感じにまたやっていくという気持ちにちょっとなった。今日も睡魔は来ない。

cats.data.ContT を試してみた

Scala Advent Calendar 2018 - 24日目の記事です。

ついに12月でアドカレ始まったかーと思ってたらもうクリスマスイブで自分の番ですよ。早いですね。 今年のブログ更新もどうせこれが最初で最後でしょう。また来年頑張ります...

さて、今回は一ヶ月ほど前に master に入り、v1.5.0-RC1 から使えるようになった cats.data.ContT について少し試した感想を。

github.com

ちなみに、継続モナドについては丁度7日に @kazzna さんがContの合成がしたいというタイトルで 公開されていたので、そちらもどうぞ。ContT はそんな継続モナドモナドトランスフォーマー版です。

ついに Cats にも ContT が

Scala においても継続モナドは実用的なツールの一つです。 自分の観測範囲では例えば、 ActionCont で有名な pab_tech さんの qiita.com jwhaco さんの qiita.com しもむらさんの labs.septeni.co.jp

などなど。特に ActionCont には公私ともにお世話になってたり。

さて「ついに Cats にも〜」って書き出しからもお分かりの通り、Scalaz には結構昔からありました。 ただ、ここ最近に至っては*1 doobie や Monix が内部実装を Scalaz から Cats へ移行するなどしてしまったため、 Web サービスやらなんやらの実用的なアプリケーションを作ろうとした際に、Cats を使って実装されたライブラリとの組み合わせが悪くなってしまったのでした。

そんな状況だったので、各々そもそも使うのを諦めたり、適当に自前実装したり、 はたまた Scalaz の実装コピって置換しただけみたいなライブラリが登場したり*2といった感じの状況だったのが、 ようやく解消されたのかなーといった感じでやったー!みたいな気持ち、のはずでした。

cats.data.ContT を試してみる

以降、登場するサンプルコードは

  • Scala: 2.12.8
  • Scalaz: 7.2.27
  • Cats: 1.5.0
  • sbt: 1.2.7

sbt consoleで REPL 起動しています。

では早速、さくっと Option モナドに積んで試してみましょう。なんら意味のないコードですが。 まずは比較の為に Scalaz から。

scala> import scalaz.ContT
import scalaz.ContT

scala> def cont(s: String): ContT[Option, Int, String] = ContT(f => f(s))
cont: (s: String)scalaz.ContT[Option,Int,String]

scala> val c = for {
     |   a <- cont("12")
     |   b <- cont(a + "24")
     | } yield b
c: scalaz.IndexedContsT[scalaz.Id.Id,Option,Int,Int,String] = IndexedContsT(scalaz.IndexedContsT$$Lambda$4064/1553542174@5aeb00bd)

scala> c.run(s => util.Try(s.toInt).toOption)
res0: Option[Int] = Some(1224)

いいかんじですね。さて、今度は Cats で同じことをやってみます。

scala> import cats.data.ContT
import cats.data.ContT

scala> def cont(s: String): ContT[Option, Int, String] = ContT(f => f(s))
cont: (s: String)cats.data.ContT[Option,Int,String]

scala> val c = for {
     |   a <- cont("12")
     |   b <- cont(a + "24")
     | } yield b
<console>:15: error: could not find implicit value for parameter M: cats.Defer[Option]
         b <- cont(a + "24")
           ^
<console>:14: error: could not find implicit value for parameter M: cats.Defer[Option]
         a <- cont("12")
           ^

おやおや、Cats の方は失敗しました。なんか暗黙の値が無いよって怒られてますね。

合成するには Defer 型クラスのインスタンスが必要

さて、エラー内容を頼りに ContT の実装を見てみると、 map/flatMap を実行する為には M[_] が Defer 型クラスのインスタンスであることが求められています。

sealed abstract class ContT[M[_], A, +B] extends Serializable {
  final def run: (B => M[A]) => M[A] = runAndThen
  protected def runAndThen: AndThen[B => M[A], M[A]]

  final def map[C](fn: B => C)(implicit M: Defer[M]): ContT[M, A, C] = {
    // ...
  }

  // ...

  final def flatMap[C](fn: B => ContT[M, A, C])(implicit M: Defer[M]): ContT[M, A, C] = {
    // ...
  }
}

Defer 型クラスは非常にシンプルで、F[_] の生成の際に中身の生成を遅らせられることを要求します。

trait Defer[F[_]] extends Serializable {
  def defer[A](fa: => F[A]): F[A]
}

適当に確認する限り、この Defer 型クラスのインスタンスは馴染みのあるところで

  • cats.Eval[A]
  • cats.Free[S[_], A]
  • cats.effect.IO[A]
  • monix.eva.Task[A]

また、F[_] が Defer のインスタンスに限り、下記もインスタンスになります。

  • cats.data.EitherT[F[_], A, B]
  • cats.data.OptionT[F[_], A]
  • cats.data.IndexedStateT[F[_], SA, SB, A]
  • cats.data.Kleisli[F[_], A, B]

といった具合で、サンプルコードの(標準ライブラリの)Option に対しては定義されておらず、 裏を返せばこれ以外には ContT を積めない(積めるけど合成できない)ということになります。えぇ...

ちなみに Defer が必要な理由として、Cats の場合は Monadインスタンスを定義する際に、 stack-safe であることが要求*3されるので、それの実現の為なはずなんですが、 そのあたりはまた別の記事で。*4

cats.Cont はどこ?

Scalaz v7.2.27*5 の Cont は

type Cont[R, A] = ContT[Id, R, A]
object Cont extends IndexedContsTInstances with IndexedContsTFunctions {
    def apply[R, A](f: (A => R) => R): Cont[R, A] = IndexedContsT[Id, Id, R, R, A](f)
}

のように定義されていて、これは (ContT に限らず) Id モナドに積んでやれば元の(?)シンプルな定義を得られるといった具合です。 ところが cats.ContT でやろうとすると、cats.Id に対する Defer 型クラスのインスタンスが存在しない*6ため、定義そのものはできるものの map/flatMap が使えず、for 式での合成ができません。

scala> type Cont[R, A] = ContT[Id, R, A]
defined type alias Cont

scala> ContT.pure[Id, String, Int](100)
res0: cats.data.ContT[cats.Id,String,Int] = FromFn(AndThen$1198828579)

scala> res0.map(_.toDouble)
<console>:22: error: could not find implicit value for parameter M: cats.Defer[cats.Id]
       res0.map(_.toDouble)
               ^

一応、Defer 型クラスのインスタンスでもある cats.Eval を使うことで回避することが可能ですが、果たして。。*7

scala> ContT.pure[Eval, String, Int](100)
res2: cats.data.ContT[cats.Eval,String,Int] = FromFn(AndThen$1654564972)

scala> res2.map(_.toDouble)
res3: cats.data.ContT[cats.Eval,String,Double] = FromFn(AndThen$627616169)

ってな具合で、使う場合はどこでもまずはモナドトランスフォーマー版から始めなきゃいけないのも手間ですね。

まとめ

使いにくいなーと思う箇所はあれど、実際は Monix の Task に積んだり、Eval でも問題なかったりで、実用上はそこまで困らない(はず)って印象です。 何はともあれ "Cats でも継続モナドが使えるようになった" という事実は大きいので、これからじゃんじゃん使い倒していくぞという気持ちです。ありがとうございます。

良いお年を


*1:といっても、数年前ですが...

*2:さすがにどうなんだ...

*3:tailRecM もそうだけど、中の人の方針的にも?

*4:そもそも Monad の制約に tailRecM を強制するのがやっぱり良くないのではって気がしなくもない

*5:https://github.com/scalaz/scalaz/blob/v7.2.27/core/src/main/scala/scalaz/package.scala#L320-L323

*6:ちゃんと調べられておらず、理論的に実装不可か、それとも頑張れば定義できるがまだ入っていないだけなのかは不明なので「現時点の実装ではできない」という事実のみ。それ以外の意味はありません。

*7:cats.Eval は評価を制御できるようにするデータ型で、モナドです。Id モナドとは別物ですが、一番シンプルで近いデータ型のはず...

近況とか

aoino.hatenablog.com

以降の話をハイライトでお伝えします。 この記事は Scala関西Summit 2017 の資料作成に追われる現実逃避息抜きに書いています。

旅行と飯と酒と Switch しかありません。進捗はどこに行ったのでしょうか。

退職します

ちょっと数日たってしまいましたが、日記です。

マーベリック株式会社を退職します。 5/2 が最終出社日で、5/31 が退職日です。

例のアレです↓ http://www.amazon.co.jp/registry/wishlist/3C4T2LY4FGFR6/ref=cm_sw_r_tw_ws_x_VJddzb0XGN15T

何してる人?

猫が大好きな、東京都内で Scala 書くお仕事してる人です。 Lens の話してたり Monocle ってライブラリのコミッターだったりする人です。 あと、猫が大好きです。

振り返って

振り返ってみて、自由な社風でとてもいい会社だったなと思ってます。 自分がコミッターになったのも、思い返してみれば OSS 活動に対する理解があった故でしたし。 直近ではとあるシステムの要件定義から始まり、構成考えてミドルウェアの検証したのち、 実装から各種テスト、デプロイ体制の整備あたりまで一通り担当できてとても自信に繋がりました。 開発/運用、アプリ/インフラなどなどにおける垣根や柵が(いい意味で)ほぼ存在してないので、 入社してから約二年半という短い期間で様々なことに取り組めましたしとても素晴らしい経験をすることが出来ました。

では何故?って話ですが、自分が数年間隔で考えてる将来の展望と比較して所謂音楽性の違い(?)みたいなのがあったのと、 長期的な観点で考えた時に環境を変えて新たな業種での開発もしてみたいと思ったのが理由です。

前向きなので特別なネタは無いですが、そのあたり聞きたい方は肉or寿司の場で(ぇ

まとめとか

とりあえず5月一杯の有給消化期間を全力でまったりと(?)進捗を出していこうかなと思っています。 次どこで何をするかは諸々確定してから改めて書く予定です。

お世話になった皆さん、本当にありがとうございました。 今後とも引き続きよろしくお願い致します。

Scala関西 Summit 2016 で Lens/Prism について発表してきた

日付変わってしまいましたが、Qiita 版 Scala Advent Calendar 2016 の12日目の記事です。

前日: grimrose さんの ScalikeJDBCを使ってAmazon Athenaへアクセスしてみた
翌日: jwhaco さんの いい感じの sbt パーサを作る

blog 記事を書くまでがイベントです。 とても遅くなってしまいましたが、10/8 に開催された Scala関西 Summit 2016 のレポートです。

Scala関西 Summit 2016 とは

関西最大級の Scala カンファレンスで、2015年に初開催。今年で二回目です。
イベントページはこちら↓↓↓

summit.scala-kansai.org

発表内容とか

タイトルは「今から始める Lens/Prism」です。
資料は SlideShare に上げてありますので見直したい方はどうぞ。

www.slideshare.net

以下、思い出とか発表の背景とか。

資料は reveal.js 使って作成しました。 当日はブラウザで index.html を開いて発表しようと考えていたんですが試しにプロジェクターと接続した瞬間レイアウト完全崩壊。 本番では発表15分前になんとなく PDF にして SlideShare に上げておいた奴を使って事なきを得たので、事前の準備大事ですよ!!!という教訓...*1

今回の発表は 46, 47 ページを境にざっくり前半と後半部分に分かれています。 前半部分は Scala で Lens/Prism を使う際のモチベに始まり、getter/setter を抽象的に考えていって Lens を導入。 法則やら合成やら Prism やら Optics Hierarchie やらをざっとなぞって完。 そして後半に「今から始める〜」とか入門編みたいなタイトルにしておきながら基礎理論的な内容として van Laarhoven Lens の話をしました。 後半に関しては正直なところ入門的な話の後にする内容ではないとは思っていたのですが、 それ以上に自分が理解した時のテンション&概念の面白さを伝えたかったのと、 途中に出てくる traverse 関数から map と foldMap を導出する話が Scala 関数デザイン&プログラミングの 12章で語られていて、 書籍からの一歩進んだ応用例みたいな位置付けとして面白そうだなと思い盛り込む事にしました。 振り返ってみると、結果的に内容に緩急もついて良かったんじゃないかなと思ってます。

他に時間やら理解度やらの都合で話せなかった Lens の歴史や別な実装である Store Commonad Lens の話などなど、 色々と面白くて興味深い内容もまだまだたくさんあるので機会があれば関連内容でまた発表したいですね。 例えば、タイムテーブルも公開され始めた 2/25, 2/26 に東京国際交流館で開催される ScalaMatsuri 2017 の二日目、アンカンファレンスとかいいかもしれませんね!!!!!

本当に発表するかはさておき、とても面白い分野(?)なので来年も引き続きお金のかからない Lens 沼に浸っていられそうな気がしています。

まとめ

関西 Scala コミュニティの盛り上がりをすごく感じましたし、とても楽しく充実した1日を過ごせました。 スタッフの皆さん本当にお疲れさまでした。ありがとうございました。次回の開催も心より楽しみにしております!

Special Thanks

開催前日に @kawachi さんと @lyrical_logical さんより資料や発表の構成についてアドバイスを頂きました。特に @lyrical_logical さんには文章や細かい言い回しから理論的な監修に至るまで長時間おつきあい頂きました。 お二人のアドバイスがなければあの資料は完成しませんでした。本当にありがとうございました。

*1:今回は資料を発表直前まで弄っててその辺怠ってた...今までは何もなかった慢心...