-
-
Save noelwelsh/514f38b704cb088d6d0e to your computer and use it in GitHub Desktop.
Free version of Invariant functor type class + Monoidal type class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package nl.mdj | |
import cats.arrow.NaturalTransformation | |
import cats.functor.Invariant | |
import cats._ | |
import cats.syntax.all._ | |
import scala.language.higherKinds | |
sealed abstract class FreeInvariant[F[_], A] extends Product with Serializable { | |
import FreeInvariant.{FA, Suspend, Zip, Imap} | |
def imap[B](f: A => B, g: B => A): FA[F, B] = Imap(this, f, g) | |
def product[B](b: FreeInvariant[F, B]): FA[F, (A, B)] = Zip(this, b) | |
def foldMap[G[_]](n: F ~> G)(implicit I: Invariant[G], M: Monoidal[G]): G[A] | |
} | |
object FreeInvariant { | |
type FA[F[_], A] = FreeInvariant[F, A] | |
private final case class Zip[F[_], A, B](fa: FA[F, A], fb: FA[F, B]) extends FA[F, (A,B)] { | |
def foldMap[G[_]](n: F ~> G) | |
(implicit I: Invariant[G], M: Monoidal[G]): G[(A,B)] = | |
M.product(fa.foldMap(n), fb.foldMap(n)) | |
} | |
private final case class Suspend[F[_], A](value: F[A]) extends FA[F, A] { | |
def foldMap[G[_]](n: F ~> G) | |
(implicit I: Invariant[G], M: Monoidal[G]): G[A] = | |
n.apply(value) | |
} | |
private final case class Imap[F[_], X, Y](value: FA[F, X], g: X => Y, f: Y => X) extends FA[F, Y] { | |
def foldMap[G[_]](n: F ~> G) | |
(implicit I: Invariant[G], M: Monoidal[G]): G[Y] = | |
I.imap(value.foldMap(n))(g)(f) | |
} | |
final def lift[F[_], A](a: F[A]): FA[F, A] = Suspend(a) | |
implicit def imap[F[_]]: Invariant[FA[F, ?]] = new Invariant[FA[F, ?]] { | |
override def imap[A, B](fa: FA[F, A])(f: (A) => B)(g: (B) => A): FA[F, B] = fa.imap(f, g) | |
} | |
implicit def monoidal[F[_]]: Monoidal[FA[F, ?]] = new Monoidal[FA[F, ?]] { | |
override def product[A, B](fa: FA[F, A], fb: FA[F, B]): FA[F, (A, B)] = fa.product(fb) | |
} | |
} | |
object Main extends App { | |
object algebra { | |
sealed trait RoutePartF[A] | |
object RoutePartF { | |
final case class IntPart[A](description: Option[String], f: Int => A, g: A => Int) extends RoutePartF[A] | |
final case class Slash[A](f: Unit => A, g: A => Unit) extends RoutePartF[A] | |
final case class Segment[A](text: String, f: Unit => A, g: A => Unit) extends RoutePartF[A] | |
} | |
} | |
object dsl { | |
import algebra._ | |
type Dsl[A] = FreeInvariant[RoutePartF, A] | |
private def lift[A](value: RoutePartF[A]): Dsl[A] = FreeInvariant.lift[RoutePartF, A](value) | |
def int: Dsl[Int] = lift(RoutePartF.IntPart(None, identity, identity)) | |
def slash: Dsl[Unit] = lift(RoutePartF.Slash(identity, identity)) | |
def segment(text: String): Dsl[Unit] = lift(RoutePartF.Segment(text, identity, identity)) | |
} | |
import dsl._ | |
import algebra._ | |
val route = (segment("test") |@| slash |@| int).tupled | |
val codec = route.foldMap(new NaturalTransformation[RoutePartF, Codec] { | |
override def apply[A](fa: RoutePartF[A]): Codec[A] = fa match { | |
case RoutePartF.IntPart(_, f, g) => Codec.int.imap(f, g) | |
case RoutePartF.Segment(text, f, g) => Codec.const(text).imap(f, g) | |
case RoutePartF.Slash(f, g) => Codec.const("/").imap(f, g) | |
} | |
}) | |
println(codec.encode(((), (), 5))) | |
trait Codec[A] { self => | |
def encode(a: A): String | |
def decode(v: String): Option[A] | |
def imap[B](f: A => B, g: B => A): Codec[B] = new Codec[B] { | |
override def encode(a: B): String = self.encode(g(a)) | |
override def decode(v: String): Option[B] = self.decode(v).map(f) | |
} | |
} | |
object Codec { | |
def const(text: String) = new Codec[Unit] { | |
override def encode(a: Unit): String = text | |
override def decode(v: String): Option[Unit] = ??? | |
} | |
val int = new Codec[Int] { | |
override def encode(a: Int): String = a.toString | |
override def decode(v: String): Option[Int] = ??? | |
} | |
implicit val invariant: Invariant[Codec] = new Invariant[Codec] { | |
override def imap[A, B](fa: Codec[A])(f: (A) => B)(g: (B) => A): Codec[B] = fa.imap(f, g) | |
} | |
implicit val monoidal: Monoidal[Codec] = new Monoidal[Codec] { | |
override def product[A, B](fa: Codec[A], fb: Codec[B]): Codec[(A, B)] = new Codec[(A, B)] { | |
override def encode(a: (A, B)): String = fa.encode(a._1) ++ fb.encode(a._2) | |
override def decode(v: String): Option[(A, B)] = for { | |
a <- fa.decode(v) | |
b <- fb.decode(v) | |
} yield a -> b | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment