Applicative Style

Applicative Functor

Applicative Functorは以下の三つの関数を持つ

def map[A, B](fa: F[A])(f: A => B): F[B] // Functor
def pure[A](a: => A): F[A]
def ap[A, B](fa: => F[A])(f: => F[A => B]): F[B]
  • Functor ← Applicative Functor ← Monadという継承関係
  • Applicative Functorは(Monadとは違って)合成に関して閉じている
    (Applicative Functorを合成すると必ずApplicative Functorになる)
  • 以上の性質からMonadを使わずにApplicative Styleで書けるなら、そのほうが良いと言われている(Haskellでは)

Example in Haskell

Haskellの場合

do
  i <- Just "a"
  j <- Just "b"
  return $ i ++ j
(++) <$> Just "a" <*> Just "b"
と書く

Example in Scala

Scalaの場合

for {
  a <- Option("a")
  b <- Option("b")
} yield a + b

これをどう書くかという話

Scalaz Apply Syntax

^(Option("a"), Option("b"))(_ + _)

Pros:

  • 中間生成オブジェクトが少なくオーバーヘッドが少ない
    「Function0 が引数の数だけ生成されるので、中間オブジェクトが本当に少ないのかは結構微妙」 by xuwei-k
  • Applicative styleをやりたいならこれで書けるものはこれで書くというのが無難

Scalaz Apply Syntax

Cons:

  • 7引数の関数までしか定義されていない(applyを直接呼べば12引数までOK)
  • 複数の型パラメータを受け取る型コンストラクタ(EitherやFunction2など)でうまく動作しない
    ^("a".right[Int], "b".right[Int])(_ + _) // not work
  • 以下のように回避することもできる
    val F = Apply[({type l[a]=Int\/a})#l]
    import F.applySyntax._
    ^("a".right,"b".right)(_ + _) // by xuwei-k

Scalaz Applicative Builder

(Option("a") |@| Option("b"))(_ + _)

Pros:

  • 複数の型パラメータを受け取る型コンストラクタでも動作する
    ("a".right[Int] |@| "b".right[Int])(_ + _) // work
  • Apply syntaxで書けないものをApplicative builderで書くのが良いかも

Cons:

  • 中間オブジェクトができるのでApply syntaxよりパフォーマンスが悪い
  • Applyの引数の制限は同じ

Scalaz Applyを直接使う

やや冗長だが直接Applyインスタンスを呼び出すことができる

Apply[Option].apply2(Option("a"), Option("b"))(_ + _)

合成もできる

val F = Apply[List] compose Apply[Option]
F.apply2(List(Option("a")), List(Option("b")))(_ + _)
// => List(Option("ab"))

Scalaz Applyを直接使う

複数の型パラメータを受け取る型コンストラクタの場合

val F = Apply[({type l[a]=Int\/a})#l]
F.apply2("a".right, "b".right)(_ + _)

Unapplyを使ってもよい

val F = Unapply.unapplyMAB2[Apply, \/, Int, String].TC
F.apply2("a".right, "b".right)(_ + _)

Effectful

effectfully(Option("a").! + Option("b").!)
val F = Unapply.unapplyMA[Monad, Option, String](optionInstance).TC
F.bind(Option("a"))(a => F.bind(Option("b"))(b => F.pure(a + b)))

と展開される

Effectful

Pros:

  • 読みやすい?
  • 引数の制限がない

Cons:

  • bindを使っているので正確に言うとApplicative styleではない
  • implicit conversionが呼ばれないバグがある
effectfully(Option("abc").!.count(_.isLetter)) // not work

Scala Idioms

idiom[Option]($(Option("a") + Option("b")))
  • aztek/scala-idioms · GitHub
  • マクロパラダイスのUntypedマクロで実装されている
  • この表記法はIdrisというプログラミング言語の「idioms notation」からいただいているらしい
idioms.option.app(
  idioms.option.map(
    (a: String) => (b: String) => a + b
  )(Option("a"))
)(Option("b"))

と展開される

Scala Idioms

型を指定する方法と

idiom[List]($(List(1,2,3) * List(4,5,6)))
// => List(4, 5, 6, 8, 10, 12, 12, 15, 18)

型クラスインスタンスを直接指定する方法がある

idiom(zipList)($(List(1,2,3) * List(4,5,6)))
// => List(4, 10, 18)

Scala Idioms

式全体が$で囲まれている場合以下のような記法もある

$[Option](Option("a") + Option("b"))

合成もできる

idiom(list $ option)($(List(Option("a")) + List(Option("b"))))

Scala Idioms

Pros:

  • Scalazがいらない
  • わりと素直に展開されてオーバーヘッドも少なさそう

Cons:

  • マクロパラダイスじゃないと使えない
  • コンテキストの型を指定しないといけない

Summary

  • Scalaz Applyはいくつか書き方を使い分けなきゃいけないのが面倒くさい
  • Effectfulは今のところ微妙
  • Scala IdiomsはUntypedマクロの参考に
  • forで十分です