Skip to content

Instantly share code, notes, and snippets.

@halcat0x15a
Last active August 29, 2015 14:17
Show Gist options
  • Save halcat0x15a/41aeed1d428e1b546e30 to your computer and use it in GitHub Desktop.
Save halcat0x15a/41aeed1d428e1b546e30 to your computer and use it in GitHub Desktop.

Free Applicative Functors

@halcat0x15a

http://arxiv.org/abs/1403.0749

はなすこと

  • Applicativeについて
  • Free Applicativeについて
  • 圏論的な

Applicative

Monadをより一般化したもの

class Functor f  Applicative f where
  pure :: a  f a
  (<*>) :: f (a  b)  f a  f b

(<*>)は順次計算して1番目の引数に2番目の引数を適用する演算

(<*>)は任意のarityの関数に適用できる

pure f <*> a <*> b

Monadより軽量に記述できる.

do
  x <- a
  y <- b
  return $ f x y

Free Monad

FreeはHaskellではとてもよく知られているデータ構造

data Free f a
  = Return a
  | Free (f (Free f a))

しかし計算構造は実行するまでわからない

Free Applicative

実装は左結合か右結合で異なるが同形

sealed trait Free[F[_], A]

case class Pure[F[_], A](a: A) extends Free[F, A]

abstract case class Apply[F[_], A]() extends Free[F, A] {
  type I
  def f: F[I => A]
  def k: Free[F, I]
}

FがFunctorのときFreeはApplicative

implicit def free[F[_]](implicit F: Functor[F]) =
  new Applicative[({ type G[A] = Free[F, A] })#G] {
    def pure[A](a: A): Free[F, A] = Pure(a)
    override def map[A, B](fa: Free[F, A])(f: A => B): Free[F, B] =
      fa match {
        case Pure(a) => Pure(f(a))
        case Apply(fa: F[A], g: Free[F, A => B]) => Apply(F.map(fa)(f), map(g)(f compose _))
      }
    def apply[A, B](fa: Free[F, A])(f: Free[F, A => B]): Free[F, B] =
      f match {
        case Pure(g) => map(fa)(g)
        case f@Apply() => Apply(f.f, apply(fa)(map(f.k)(g => (a: A) => g(_: B)(a))))
      }
  }

Freeの(<*>)はListの(++)のように定義される

Example: Option parser

Free Monadより有用なFree Applicativeの例

create_user --username halcat0x15a --fullname SanshiroYoshida --id 346
case class User(username: String, fullname: String, id: Int)

case class Opt[A](name: String, default: Option[A], reader: String => Option[A])

def username: Free[Opt, String] = one(Opt("username", None, Some[String]))
def fullname: Free[Opt, String] = one(Opt("fullname", Some(""), Some[String]))
def id: Free[Opt, Int] = one(Opt("id", None, s => allCatch.opt(s.toInt)))

def user: Free[Opt, User] = Applicative.map(username, fullname, id)(User)
  • 作用が独立
  • 解析が可能

Left adjoint

「Free Applicativeは単なるApplicativeの圏から自己関手の圏への忘却関手の左随伴だよ, なにか問題でも?」

trait Nat[F[_], G[_]] {
  def apply[A]: F[A] => G[A]
}

def lift[F[_], G[_]](f: Nat[F, G])(implicit F: Functor[F], G: Functor[G]): Nat[({ type H[A] = Free[F, A] })#H, ({ type H[A] = Free[G, A] })#H] =
  new Nat[({ type H[A] = Free[F, A] })#H, ({ type H[A] = Free[G, A] })#H] {
    def apply[A] = {
      case Pure(x) => Pure(x)
      case a@Apply() => Apply(f[a.I => A](a.f), lift(f).apply[a.I](a.k))
    }
  }

FreeはFunctorからApplicativeを構成できる

def one[F[_], A](fa: F[A])(implicit F: Functor[F]): Free[F, A] =
  Apply(F.map(fa)(const[A, Unit]), Pure(()))

随伴

Hom_A(Free f, g)とHom_F(f, g)が同形

def raise[F[_], G[_]](f: Nat[F, G])(implicit G: Applicative[G]): Nat[({ type H[A] = Free[F, A] })#H, G] =
  new Nat[({ type H[A] = Free[F, A] })#H, G] {
    def apply[A] = {
      case Pure(a) => G.pure(a)
      case a@Apply() => G(raise(f).apply[a.I](a.k))(f[a.I => A](a.f))
    }
  }

def lower[F[_], G[_]](f: Nat[({ type H[A] = Free[F, A] })#H, G])(implicit F: Functor[F], G: Applicative[G]): Nat[F, G] =
  new Nat[F, G] {
    def apply[A] = f[A] compose one[F, A]
  }

raiseはFreeを解析できる

def parserDefault = raise(new Nat[Opt, Option] { def apply[A] = _.default })

def allOptions = raise[Opt, ({ type F[A] = List[String] })#F](new Nat[Opt, ({ type F[A] = List[String] })#F] { def apply[A] = _.name :: Nil })(Monoid.list.applicative)

まとめ

  • Free Applicativeは構造の解析が可能
  • Free Applicativeは単なるApplicativeの圏から自己関手の圏への忘却関手の左随伴
@xuwei-k
Copy link

xuwei-k commented Mar 21, 2015

FがFunctorのときFreeはApplicative

FがFunctorのとき という条件は(少なくともScalazの場合)必要なかったような?

https://github.com/scalaz/scalaz/blob/v7.1.1/core/src/main/scala/scalaz/FreeAp.scala#L4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment