Skip to content

Instantly share code, notes, and snippets.

@oxbowlakes
Last active April 21, 2016 15:00
Show Gist options
  • Save oxbowlakes/0b54a97ee28caecf16af to your computer and use it in GitHub Desktop.
Save oxbowlakes/0b54a97ee28caecf16af to your computer and use it in GitHub Desktop.
object Lenses {
/*
This gist shows that LensP (modification inside a functor) is isomorphic to LensR (getter and setter)
In particular, it shows that you can derive the signature: S => A from (A => F[A]) => (S => F[S])
This is puzzling at first glance, because it gives rise to the question:
"if I end up with an F[S], how can I get a A out of it?"
We use the Const type where Const[A, X] = X. Note that Const[_, X] is a functor, but will always return
the value X which was originally stored in it!
*/
/* traditional lens, with a getter and a setter */
trait LensR[S, A] {
def viewR: S => A
def setR(a: A): S => S
}
import language.higherKinds
trait Functor[F[_]] { def fmap[A, B](fa: F[A])(f: A => B): F[B] }
object Functor { def apply[F[_]: Functor]: Functor[F] = implicitly }
/* Lens codification using a single method, modify an S by modifying its inner A within a functor */
trait LensP[S, A] { def run[F[_]: Functor](modF: A => F[A]): S => F[S] }
/* Note we wish to prove that LensR is isomorphic to LensP */
/* Separate the machinery we will use to prove the isomorphism */
object Machinery {
import language.implicitConversions
import language.reflectiveCalls
/* Id machinery */
object Identity {
type Id[A] = A
implicit def ToId[A](a: A): Id[A] = a
implicit def FromId[A](ida: Id[A]): A = ida
implicit val IdFunctor: Functor[Id] = new Functor[Id] { def fmap[A, B](fa: Id[A])(f: A => B): Id[B] = f(fa) }
}
/* Const machinery */
object Constant {
type Const[A, X] = X
/* Note these are not implicit, for clarity, but they easily could be */
def constPut[A, B](b: B): Const[A, B] = b
def constGet[A, B](c: Const[A, B]): B = c
def ConstFunctor[X] = new Functor[({ type l[a] = Const[a, X]})#l] {
override def fmap[A, B](fa: Const[A, X])(f: A => B): Const[B, X] = constPut[B, X](fa /* fa is an X */ )
}
}
}
/* Now we are ready to prove the isomorphism */
/* All LensPs are LensRs */
def LensP_Is_LensR[S, A](lensP: LensP[S, A]): LensR[S, A] = new LensR[S, A] {
/* This is pretty obvious: we want to use the Identity functor */
import Machinery.Identity._
override def setR(a: A): S => S
= s => lensP.run[Id](_ => a).apply(s)
/*
Here is the magic: "how do we get an A out of an F[S]?"
If we choose F[S] = Const[A, S], then we have a functor which we can get an A out of
*/
import Machinery.Constant._
override def viewR: S => A
= s => constGet[S, A](lensP.run[({type l[a] = Const[a, A]})#l](a => constPut[S, A](a))(ConstFunctor[A])(s))
/* with implicit Const, can be simplified to lensP.run[({type l[a] = Const[a, A]})#l](a => a).apply(s) */
}
/* All LensRs are LensPs */
def LensR_Is_LensP[S, A](lensR: LensR[S, A]): LensP[S, A] = new LensP[S, A] {
override def run[F[_] : Functor](modF: A => F[A]): S => F[S] = {
s => Functor[F].fmap(modF(lensR.viewR(s)))(a => lensR.setR(a)(s))
}
}
/* LensPs can be trivially composed */
def composeLensP[S, A, B](lensP1: LensP[S, A], lensP2: LensP[A, B]): LensP[S, B] = new LensP[S, B] {
override def run[F[_] : Functor](modF: B => F[B]): S => F[S] = {
lensP1 run (lensP2 run modF)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment