とりあえず何かを覚えたらfizzbuzzにするクセやめたい
import cats.syntax.all._ import cats.implicits._ extension (d: Int) def ~>(s: String)(n: Int) = (n % d == 0).guard[Option] as s val fizzbuzz = (((2 ~> "fizz" &&& 3 ~> "buzz") >>> (_.toList.combineAll)) &&& (_.toString)) >>> { case (fb, n) => fb.getOrElse(n) } 1 to 50 map fizzbuzz
Arrowを使うと直線的に処理が記述できてありがたい感じがする。1ツイートにも収まるし。
import cats.syntax.all._
— @windymelt@mstdn.takuya-a.net に引っ越しました (@windymelt) 2023年3月3日
import cats.implicits._
extension (d: Int) def ~>(s: String) = { (_: Int).some filter (_ % d == 0) as s }
val fizzbuzz = (((2 ~> "fizz" &&& 3 ~> "buzz") >>> (_.toList.combineAll)) &&& (_.toString)) >>> { case (fb, n) => fb.getOrElse(n) }
要素
extension (d: Int) def ~>(s: String)(n: Int) = (n % d == 0).guard[Option] as s
extension method
extension (d: Int) def ~>(s: String)
で、 d ~> s
という表現が可能になる。Scala3テク。
(n: Int) = (n % d == 0).guard[Option] as s
関数を返したいので、extension defの引数をもう一つつけてある。
guard
はcatsがBoolean
に生やしてくれるメソッドで、b.guard[Option]
と書くと
b match { case true => Some(true) case false => None }
と同じ。
as
は内容を破棄して指定した値を使わせるメソッド。a as b
と書くとa.map(_ => b)
と同じ。
総合すると、2 ~> "foo"
と書くと、Int => Option[String]
を返してくれる。そして、渡された値が2で割れる場合にのみSome("foo")
が返るようになる。
val fizzbuzz = (((2 ~> "fizz" &&& 3 ~> "buzz") >>> (_.toList.combineAll)) &&& (_.toString)) >>> { case (fb, n) => fb.getOrElse(n) }
後半。
2 ~> "fizz" &&& 3 ~> "buzz"
2つの関数をArrowのパワーで並列に結合する。&&&
は別名merge
で、A => B
とA => C
を受け取るとA => (B, C)
にしてくれる。この場合はInt => (Option[String], Option[String])
が得られる。
>>> (_.toList.combineAll)
>>>
はandThen
のエイリアス。
_.toList.combineAll
の型を明示すると(pair: (Option[String], Option[String])) => pair.toList.combineAll
になっている。pair.toListによって
List[Option[String]]に変換されるので、これを
combineAll`している。
知っていましたか?String
はモノイドになります。そして、Option[String]
も自動的にモノイドになるんですね。便利。そんで、モノイドのリストはcombineAll
するとx1 |+| x2 |+| x3 |+| ... |+| xn
の形で畳み込まれる。結果として、以下のような感じになる:
(None, None)
はNone
(Some("fizz"), None)
はSome("fizz")
(None, Some("buzz"))
はSome("buzz")
(Some("fizz"), Some("buzz"))
はSome("fizzbuzz")
&&& (_.toString)
((2 ~> "fizz" &&& 3 ~> "buzz") >>> (_.toList.combineAll))
の型はInt => Option[String]
になっている。さらにここにmerge
して、(_.toString)
、つまりInt => String
をくっつけている。
型はInt => (Option[String], String)
になる。
>>> { case (fb, n) => fb.getOrElse(n) }
ここまで来れば説明不要でしょう。
汚い図ですが、このような処理を合成しているわけですね。
関連記事
こいつずっとfizzbuzz書いてんな