Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

(追記あり)CatsでReaderになるfunctionをいい感じに合成したいけど微妙

tl;dr

  • (f, g) mapN (_ compose _) しましょう
  • f -> g mapN (_ <<< _)って書くとちょっとかっこいい

つづき

最近ずっとCatsの記事を書いているな……

先日、(->) r がApplicativeになるという話をした。

blog.3qe.us

んで、この(->) rが2つあるときに、これらを合成したい。

import cats._
import cats.implicits._

object Foo

val f = (r: Foo.type) => (n: Int) => 2 * n
// f: Foo.type => Int => Int = <function1>
val g = (r: Foo.type) => (n: Int) => 1 + n
// g: Foo.type => Int => Int = <function1>

val fg = f.product(g) map { case ff -> gg => ff compose gg }
// fg: Foo.type => Int => Int = scala.Function1$$Lambda$9550/0x000000084258e840@2cae8b5d
fg(Foo)(100)
// res0: Int = 202

直接composeを呼ぶことができないので、いったんproductにしてからcomposeしている。

ちなみに、|+||-|は動作するみたいだ:

val fg2 = f |+| g
// fg2: Foo.type => Int => Int = cats.kernel.instances.Function1Semigroup$$Lambda$9573/0x00000008425ac840@cbd5065
fg2(Foo)(100)
// res1: Int = 301

val fg3 = f |-| g
// fg3: Foo.type => Int => Int = cats.kernel.instances.Function1Semigroup$$Lambda$9573/0x00000008425ac840@6c6b9f00
fg3(Foo)(100)
// res2: Int = 99

せっかくなら|compose| みたいなのも用意してほしい。

ところで、arrowの機能を使うとちょっとばかりかっこ良くなる:

val fg4 = (f &&& g) >>> { case ff -> gg => gg >>> ff }
// fg4: Foo.type => Int => Int = scala.Function1$$Lambda$9550/0x000000084258e840@5ec28232
fg4(Foo)(100)
// res3: Int = 202

>>>andThenになるので、composeを置換するにはgg >>> ffと書かなければならない。

なんとなくApplicativeのなんかが使えそうな気がするのだけれど、<*>とかはちょっと様子が変わってしまうし、mapも違う。

追記

関数をReaderに押し込んで、Reader同士のapにするとうまく動いてそうだけれどすごく手間がかかっている。

import cats.data.Reader
import cats._
import cats.implicits._

object Foo

val f = (r: Foo.type) => (n: Int) => 2 * n
// f: Foo.type => Int => Int = <function1>
val g = (r: Foo.type) => (n: Int) => 1 + n
// g: Foo.type => Int => Int = <function1>

val c = (f: Int => Int) => (g: Int => Int) => f compose g
// c: (Int => Int) => (Int => Int) => Int => Int = <function1>

val ff = Reader(f)
// ff: Reader[Foo.type, Int => Int] = Kleisli(run = <function1>)
val gg = Reader(g)
// gg: Reader[Foo.type, Int => Int] = Kleisli(run = <function1>)
val cc = c.pure[Reader[Foo.type, *]]
// cc: data.Kleisli[[A]A, Foo.type, (Int => Int) => (Int => Int) => Int => Int] = Kleisli(run = cats.data.KleisliFunctions$$Lambda$9921/0x000000084262c040@398e2b5f)
val composed = cc <*> ff <*> gg
// composed: data.Kleisli[[A]A, Foo.type, Int => Int] = Kleisli(run = cats.data.Kleisli$$Lambda$10027/0x000000084261c840@6822855f)
composed(Foo)(100)
// res4: Int = 202

追記2

うんうんうなっていたら、複数名に助けてもらった。

map2mapN を使うと良さそうだった。

import cats._
import cats.implicits._

object Foo

val f = (r: Foo.type) => (n: Int) => n * 2
// f: Foo.type => Int => Int = <function1>
val g = (r: Foo.type) => (n: Int) => n + 1
// g: Foo.type => Int => Int = <function1>
val fg = f.map2(g)(_ compose _)
// fg: Foo.type => Int => Int = cats.instances.Function1Instances$$anon$7$$Lambda$10702/1505168021@255f487b
val fg2 = (f, g) mapN (_ compose _)
// fg2: Foo.type => Int => Int = scala.Function1$$Lambda$11679/1409117269@7168031a

fg(Foo)(100)
// res0: Int = 202
fg2(Foo)(100)
// res1: Int = 202

非常に綺麗!!

Scalaはその仕組み上liftするのが面倒なのでmapNでどうliftするのかを暗に伝えるやりかたのほうがやりやすいみたいだ。

liftした関数compose2 があったとして、これは compose2 <*> f <*> gと等価だ。

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?