free functor/monad

functional programmingscala

An exploration of natural transformations between functors in functional programming, introducing the concept of Free functors and demonstrating how to implement conversions between different container types like Monix Task, Scala Future, and Java CompletableFuture.

2019-01-02

Functor可以用map改变F[]里面的type
Monad可以用flatMap改变M[
]里面的type
那么是否可以保持容器里面的type不变,改变容器的类型。这是一个functor到functor的morphism,是 natural transformation。

假如在某个项目中,同时用了Monix的Task,scala collection的Future/Promise,java的CompletableFuture, 想要在这些容器之间转换,就需要用到natural transformation。

如何实现在functor之间转换

用一个Free type class,Free像其他的functor一样,都有一个map方法,不过这个map方法不是马上执行的, 而是有一个延迟(more on this later)。

sealed triat Free[S[_], A] {
  def map[B](f: A => B): Free[S, B] = 
    Free.Mapped(this, f)
}
object Free {
  final case class Point[S[_], A](a: A) extends Free[S, A]
  final case class Mapped[S[_], A, B](fa: F[A], f: A => B) extends Free[S, B]
}

注意Mapped的fa参数类型是F[A], 也就是Mapped(fa: Future[A], f: A => B)可以是一个Free[CompletableFuture[_], B]
Mapped相当于创建了一个object记下了原来的所有内容和f,并没有马上应用f。
Point是最原始的状态,从来没有被map过。
叫Free的原因是因为他能随意转换成任意的functor,例如转化成一个Future

object futureInterpreter {
  def apply[S[_], A](free: Free[S, A]): Future[A] = 
    free match {
      case Free.Pure(a) =>
        Future.successful(a)
      case Free.Mapped(fa, f) =>
        futureInterpreter(fa).map(f)
    }
}

例如转换成一个Id

object idInterpreter {
  def apply[S[_], A](free: Free[S, A]): Id[A] = 
    free match {
      case Free.Pure(a) => Id(a)
      case Free.Mapped(fa, f) => idInterpreter(fa).map(f)
    }
}

回到最开始的问题,如果要把Future转换成Promise,可以把Future[A]包装成一个Free[Future, A]。 然后用一个PromiseInterpreter把Free转换成Promise

cats中的functionK

functionK是higher-kind-function,跟natural transformation是同一个概念,scalaz里面叫natural transformation。
cats文档里面用了一个DSL的例子,在这个例子中,是要把DSL(KVStoreA)包装成Free[KVStoreA, A],
然后把这个Free转换成Id/Future

定义DSL中所有的语法ADT

sealed trait KVStoreA[A]
case class Put[T](key: String, value: T) extends KVStoreA[Unit]
case class Get[T](key: String) extends KVStoreA[Option[T]]
case class Delete(key: String) extends KVStoreA[Unit]

包装成Free,这样就可以把KVStoreA转换成任意的functor

type KVStore[A] = Free[KVStoreA, A]

// Put returns nothing (i.e. Unit).
def put[T](key: String, value: T): KVStore[Unit] =
  liftF[KVStoreA, Unit](Put[T](key, value))

// Get returns a T value.
def get[T](key: String): KVStore[Option[T]] =
  liftF[KVStoreA, Option[T]](Get[T](key))

// Delete returns nothing (i.e. Unit).
def delete(key: String): KVStore[Unit] =
  liftF(Delete(key))

// Update composes get and set, and returns nothing.
def update[T](key: String, f: T => T): KVStore[Unit] =
  for {
    vMaybe <- get[T](key)
    _ <- vMaybe.map(v => put[T](key, f(v))).getOrElse(Free.pure(()))
  } yield ()

因为KVStore是一个free monad,所以可以像monad一样compose起来,实际上是记录了原始的信息和所有的 map/flatMap方法。

def program: KVStore[Option[Int]] =
  for {
    _ <- put("wild-cats", 2)
    _ <- update[Int]("wild-cats", (_ + 12))
    _ <- put("tame-cats", 5)
    n <- get[Int]("wild-cats")
    _ <- delete("tame-cats")
  } yield n

最后可以用compiler(interpreter)把Free monad转换成任意的monad

def impureCompiler: KVStoreA ~> Id  =
  new (KVStoreA ~> Id) {

    // a very simple (and imprecise) key-value store
    val kvs = mutable.Map.empty[String, Any]

    def apply[A](fa: KVStoreA[A]): Id[A] =
      fa match {
        case Put(key, value) =>
          println(s"put($key, $value)")
          kvs(key) = value
          ()
        case Get(key) =>
          println(s"get($key)")
          kvs.get(key).map(_.asInstanceOf[A])
        case Delete(key) =>
          println(s"delete($key)")
          kvs.remove(key)
          ()
      }
  }

这里的KVStoreA ~> Id是FunctionK[KVStoreA, Id]的alias

Free monad还有一个延迟执行的功能,当写program的时候,没有实际的side-effect发生。
只有在foldMap了之后,才有side-effect

// S[_]是KVStoreA[_], M[_]是Id[_]
// 把一个S[Option[Int]]转换成了M[Option[Int]]
val result: Option[Int] = program.foldMap(impureCompiler)