Working through a Free/Cofree example

129 views
Skip to first unread message

eric thul

unread,
May 4, 2013, 8:05:57 PM5/4/13
to sca...@googlegroups.com
hi All,

I have example (below) of a Free language along with its dual. I was looking into using the zapWith functionality on the Free type to produce a result. However, I am a bit stumped as to why the call to zapWith (below) yields a stack overflow instead of the desired result. There must be something I a missing here, any pointers would be most appreciated.

Also, I am curious whether the usage of Cofree in the example below is on the mark or not. What I mean is that I would like to perhaps yield an optional value when I access the id from the DB type. For example, I am not sure I understand how this might work if I wanted to call `db.get(id)` as opposed to just `db(id)`.

Thanks,
 -Eric

PS - This is using scalaz 7.0.0 on scala 2.10.0


import scalaz.{-\/, \/-, \/, Cofree, Free, Functor, Zap}
import Free.{Gosub, Return, Suspend}

object tester {
  type Foo = String

  sealed trait FooLanguage[+A]
  case class GetFoo[+A](id: String, h: Foo => A) extends FooLanguage[A]

  sealed trait FooLanguageDual[+A]
  case class GetFooDual[+A](a: A, k: String => Foo) extends FooLanguageDual[A]

  implicit val fooLanguageFunctor: Functor[FooLanguage] =
    new Functor[FooLanguage] {
      def map[A, B](a: FooLanguage[A])(f: A => B): FooLanguage[B] = a match {
        case GetFoo(i, h) => GetFoo(i, x => f(h(x)))
      }
    }

  implicit val fooLanguageDualFunctor: Functor[FooLanguageDual] =
    new Functor[FooLanguageDual] {
      def map[A, B](a: FooLanguageDual[A])(f: A => B): FooLanguageDual[B] = a match {
        case GetFooDual(a, k) => GetFooDual(f(a), k)
      }
    }

  implicit val fooLanguageDualZap: Zap[FooLanguage, FooLanguageDual] =
    new Zap[FooLanguage, FooLanguageDual] {
      def zapWith[A, B, C](fa: FooLanguage[A], gb: FooLanguageDual[B])(f: (A, B) => C) = (fa, gb) match {
        case (GetFoo(i, h), GetFooDual(b, k)) => f(h(k(i)), b)
      }
    }

  def getFoo(id: String): Free[FooLanguage, Foo] =
    Suspend[FooLanguage, Foo](GetFoo(id, Return[FooLanguage, Foo](_)))

  type DB = Map[String, Foo]

  val db: DB = Map("test" -> "foo")

  def run(db: DB): Cofree[FooLanguageDual, DB] =
    Cofree.unfoldC[FooLanguageDual, DB](db) { db =>
      GetFooDual(db, db(_))
    }

  val program =
    for {
      x <- getFoo("test")
    } yield s"howdy $x"

  // Stackoverflows
  lazy val res: String = program.zapWith(run(db))((a, _) => a)

  def run2[A](p: Free[FooLanguage, A], db: DB): A =
    p.resume.fold({
      case GetFoo(i, h) => run2(h(db(i)), db)
    }, a => a)

  // Produces "howdy foo"
  lazy val res2: String = run2(program, db)
}

eric thul

unread,
May 13, 2013, 7:27:01 PM5/13/13
to sca...@googlegroups.com
hi All,

To steer the topic of this thread in somewhat of a different direction, I have been considering integrating Free monads and little languages into an application, and I am curious about anyone's experience going this route. Let's say that there would be at least a few languages in the application that one might want to compose to produce larger Free monad language-based APIs. 

For those who have done this before, what are your thoughts on this approach? How did it work out at the end of the day? Were there any gotchas that came up along the way?

I would very much appreciate any thoughts or feedback on the subject.

Best,
 -Eric

Runar Bjarnason

unread,
May 13, 2013, 8:23:46 PM5/13/13
to sca...@googlegroups.com
I am taking an approach like that in building an API for accessing CouchDB. So far it's working really well. For example, we can have different algebras for reading and writing a database, and use the coproduct of the two for read/write access. Also, I have different languages for "things that operate on databases at the global couch level", and "things that operate on individual databases".

Sorry I don't have an answer for your first (free/cofree) question. I haven't had the time recently to look into it.

Runar



--
You received this message because you are subscribed to the Google Groups "scalaz" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scalaz+un...@googlegroups.com.
To post to this group, send email to sca...@googlegroups.com.
Visit this group at http://groups.google.com/group/scalaz?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

eric thul

unread,
May 13, 2013, 9:28:53 PM5/13/13
to sca...@googlegroups.com
Thank you Runar for your response, that sounds great. I really like the approach and from what I've been working with it seems to allow for a very nice API.

Also, no worries pertaining to my free/cofree question. I will be taking another round at it.

Best,
 -Eric

eric thul

unread,
May 14, 2013, 1:01:16 PM5/14/13
to sca...@googlegroups.com
Regarding taking the coproduct of two algebras, is it the scalaz.Coproduct that you are referring to?

I was looking into this, but I am running into a variance issue using that with Free.

I have a complete example below, but it seems that the issue is caused since I define the coproduct  of the algebras as

type FooBarLang[A] = Coproduct[FooLanguage, BarLanguage, A]

instead of

type FooBarLang[+A] = Coproduct[FooLanguage, BarLanguage, A]

Would it be feasible define Coproduct as `sealed trait Coproduct[F[+_], G[+_], +A]`? However, perhaps there is a better way to go about this.

Thanks,
 -Eric


PS - Example


import scalaz.{-\/, \/-, \/, ~>, Cofree, Free, Functor, Zap}
import Free.{Gosub, Return, Suspend}

object tester {
  type Foo = String
  type Bar = String

  sealed trait FooLanguage[+A]
  case class GetFoo[+A](id: String, h: Foo => A) extends FooLanguage[A]

  sealed trait BarLanguage[+A]
  case class GetBar[+A](id: String, h: Bar => A) extends BarLanguage[A]

  implicit val fooLanguageFunctor: Functor[FooLanguage] =
    new Functor[FooLanguage] {
      def map[A, B](a: FooLanguage[A])(f: A => B): FooLanguage[B] = a match {
        case GetFoo(i, h) => GetFoo(i, x => f(h(x)))
      }
    }

  implicit val barLanguageFunctor: Functor[BarLanguage] =
    new Functor[BarLanguage] {
      def map[A, B](a: BarLanguage[A])(f: A => B): BarLanguage[B] = a match {
        case GetBar(i, h) => GetBar(i, x => f(h(x)))
      }
    }

  def getFoo(id: String): Free[FooLanguage, Foo] =
    Suspend[FooLanguage, Foo](GetFoo(id, Return[FooLanguage, Foo](_)))

  def getBar(id: String): Free[BarLanguage, Bar] =
    Suspend[BarLanguage, Bar](GetBar(id, Return[BarLanguage, Foo](_)))

  // Cause of the issue using A and not +A
  type FooBarLang[A] = Coproduct[FooLanguage, BarLanguage, A]

  def foo = new (FooLanguage ~> FooBarLang) {
    def apply[A](foo: FooLanguage[A]) = Coproduct.leftc(foo)
  }

  def bar = new (BarLanguage ~> FooBarLang) {
    def apply[A](bar: BarLanguage[A]) = Coproduct.rightc(bar)
  }

  //[error]inferred kinds of the type arguments (tester.FooBarLang) do
  //not conform to the expected kinds of the type parameters (type T).
  //[error] tester.FooBarLang's type parameters do not match type T's
  //expected parameters: [error] type A is invariant, but type _ is
  //declared covariant
  //[error]   val test = getFoo("a").mapSuspension(foo)
  //[error]                          ^
  val test = getFoo("a").mapSuspension(foo)
}

On Monday, May 13, 2013 8:23:46 PM UTC-4, Runar Bjarnason wrote:
Reply all
Reply to author
Forward
0 new messages