Skip to content

Instantly share code, notes, and snippets.

@bvenners
Last active May 30, 2016 17:46
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bvenners/7e23d79aee56c40ce045f0f062035dc0 to your computer and use it in GitHub Desktop.
Save bvenners/7e23d79aee56c40ce045f0f062035dc0 to your computer and use it in GitHub Desktop.
:paste
type ErrorMessage = String
trait LeftBiased
trait RightBiased
sealed abstract class Or[+G, +B] extends LeftBiased {
def map[H](f: G => H): H Or B
def badMap[C](f: B => C): G Or C
}
sealed abstract class Xor[+L, +R] extends RightBiased {
def map[S](f: R => S): L Xor S
}
object Or {
trait B[BAD] {
/**
* Type member that provides a curried alias to <code>G</code> <code>Or</code> <code>B</code>.
*
* <p>
* See the main documentation for trait <code>B</code> for more detail.
* </p>
*/
type G[GOOD] = GOOD Or BAD
}
trait G[GOOD] {
/**
* Type member that provides a curried alias to <code>G</code> <code>Or</code> <code>B</code>.
*
* <p>
* See the main documentation for trait <code>G</code> for more detail.
* </p>
*/
type B[BAD] = GOOD Or BAD
}
}
final case class Good[+G](g: G) extends Or[G, Nothing] {
def map[H](f: G => H): H Or Nothing = Good(f(g))
def badMap[C](f: Nothing => C): G Or C = this.asInstanceOf[G Or C]
}
final case class Bad[+B](b: B) extends Or[Nothing,B] {
def map[H](f: Nothing => H): H Or B = this.asInstanceOf[H Or B]
def badMap[C](f: B => C): Nothing Or C = Bad(f(b))
}
object Xor {
final case class Left[+L](el: L) extends Xor[L, Nothing] {
def map[S](f: Nothing => S): L Xor S = this.asInstanceOf[L Xor S]
}
final case class Right[+R](ar: R) extends Xor[Nothing,R] {
def map[S](f: R => S): Nothing Xor S = Right(f(ar))
}
}
val or: Int Or ErrorMessage = Good(3)
val xor: String Xor Int = Xor.Right(3)
trait Functor[Context[_]] extends Any {
/**
* Applies the given function to the given value in context, returning the result in
* the context.
*
* <p>
* See the main documentation for this trait for more detail.
* </p>
*/
def map[A, B](ca: Context[A])(f: A => B): Context[B]
}
/**
* Companion object for trait <a href="Functor.html"><code>Functor</code></a>.
*/
object Functor {
/**
* Adapter class for <a href="Functor.html"><code>Functor</code></a>
* that wraps a value of type <code>Context[A]</code> given an
* implicit <code>Functor[Context]</code>.
*
* @param underlying The value of type <code>Context[A]</code> to wrap.
* @param functor The captured <code>Functor[Context]</code> whose behavior
* is used to implement this class's methods.
*/
class Adapter[Context[_], A](val underlying: Context[A])(implicit val functor: Functor[Context]) {
/**
* A mapping operation that obeys the identity and composition laws.
*
* See the main documentation for trait <a href="Functor.html"><code>Functor</code></a> for more detail.
*/
def map[B](f: A => B): Context[B] = functor.map(underlying)(f)
}
/**
* Implicitly wraps an object in a <code>Functor.Adapter[Context, A]</code>
* so long as an implicit <code>Functor[Context]</code> is available.
*/
implicit def adapters[Context[_], A](ca: Context[A])(implicit ev: Functor[Context]): Functor.Adapter[Context, A] =
new Adapter(ca)(ev)
/**
* Summons an implicitly available Functor[Context].
*
* <p>
* This method allows you to write expressions like <code>Functor[List]</code> instead of
* <code>implicitly[Functor[List]]</code>.
* </p>
*/
def apply[Context[_]](implicit ev: Functor[Context]): Functor[Context] = ev
private class OrFunctor[BAD] extends Functor[Or.B[BAD]#G] {
override def map[G, H](ca: G Or BAD)(f: G => H): H Or BAD = ca.map(f)
}
implicit def orFunctor[BAD]: Functor[Or.B[BAD]#G] = new OrFunctor[BAD]
private class ListFunctor extends Functor[List] {
override def map[A, B](ca: List[A])(f: (A) => B): List[B] = ca.map(f)
}
implicit val listFunctor: Functor[List] = new ListFunctor
private class OptionFunctor extends Functor[Option] {
override def map[A, B](ca: Option[A])(f: (A) => B): Option[B] = ca.map(f)
}
implicit val optionFunctor: Functor[Option] = new OptionFunctor
private class XorFunctor[LEFT] extends Functor[({type l[R]=Xor[LEFT,R]})#l] {
override def map[R, S](ca: LEFT Xor R)(f: R => S): LEFT Xor S = ca.map(f)
}
implicit def xorFunctor[LEFT]: Functor[({type l[R]=Xor[LEFT,R]})#l] = new XorFunctor[LEFT]
}
def example[Ctx[_], AA, BB](ca: Ctx[AA])(f: AA => BB)(implicit functor: Functor[Ctx]): Ctx[BB] = functor.map(ca)(f)
def example[Ctx[x, y] <: LeftBiased, LL, RR, MM](ca: Ctx[LL, RR])(f: LL => MM)(implicit functor: Functor[({type l[L]=Ctx[L,RR]})#l]): Ctx[MM, RR] =
example[({type l[L]=Ctx[L,RR]})#l, LL, MM](ca)(f)(functor) // forward to the overloaded arity 1 form
def example[Ctx[x, y] <: RightBiased, LL, RR, SS](ca: Ctx[LL, RR])(f: RR => SS)(implicit functor: Functor[({type l[R]=Ctx[LL,R]})#l]): Ctx[LL, SS] =
example[({type l[R]=Ctx[LL,R]})#l, RR, SS](ca)(f)(functor) // forward to the overloaded arity 1 form
example(List(1, 2, 3))((i: Int) => i + 1)
example(Some(3): Option[Int])((i: Int) => i + 1)
example(None: Option[Int])((i: Int) => i + 1)
example(or)((i: Int) => i + 1)
example(xor)((i: Int) => i + 1)
example(Bad("oops"): Int Or ErrorMessage)((i: Int) => i + 1)
example(Xor.Left("oops"): String Xor Int)((i: Int) => i + 1)
class BadOrFunctor[GOOD] extends Functor[Or.G[GOOD]#B] {
override def map[B, C](ca: GOOD Or B)(f: B => C): GOOD Or C = ca.badMap(f)
}
def badOrFunctor[GOOD] = new BadOrFunctor[GOOD]
example[Or.G[Int]#B, ErrorMessage, ErrorMessage](or)((s: String) => s.toUpperCase)(badOrFunctor)
example[Or.G[Int]#B, ErrorMessage, ErrorMessage](Bad("oops"): Int Or ErrorMessage)((s: String) => s.toUpperCase)(badOrFunctor)
// If :load the above into the REPL, then repaste the invocations of example, you'll see:
scala> example(List(1, 2, 3))((i: Int) => i + 1)
res1: List[Int] = List(2, 3, 4)
scala> example(Some(3): Option[Int])((i: Int) => i + 1)
res2: Option[Int] = Some(4)
scala> example(None: Option[Int])((i: Int) => i + 1)
res3: Option[Int] = None
scala> example(or)((i: Int) => i + 1)
res4: Or[Int,ErrorMessage] = Good(4)
scala> example(xor)((i: Int) => i + 1)
res5: Xor[String,Int] = Right(4)
scala> example(Bad("oops"): Int Or ErrorMessage)((i: Int) => i + 1)
res6: Or[Int,ErrorMessage] = Bad(oops)
scala> example(Xor.Left("oops"): String Xor Int)((i: Int) => i + 1)
res7: Xor[String,Int] = Left(oops)
scala> class BadOrFunctor[GOOD] extends Functor[Or.G[GOOD]#B] {
| override def map[B, C](ca: GOOD Or B)(f: B => C): GOOD Or C = ca.badMap(f)
| }
defined class BadOrFunctor
scala> def badOrFunctor[GOOD] = new BadOrFunctor[GOOD]
badOrFunctor: [GOOD]=> BadOrFunctor[GOOD]
scala> example[Or.G[Int]#B, ErrorMessage, ErrorMessage](or)((s: String) => s.toUpperCase)(badOrFunctor)
res8: Or[Int,ErrorMessage] = Good(3)
scala> example[Or.G[Int]#B, ErrorMessage, ErrorMessage](Bad("oops"): Int Or ErrorMessage)((s: String) => s.toUpperCase)(badOrFunctor)
res9: Or[Int,ErrorMessage] = Bad(OOPS)
// Either is unbiased, so you get a compiler error
// example(Right(3): Either[String, Int])
//<console>:15: error: overloaded method value example with alternatives:
// [Ctx[x, y] <: RightBiased, LL, RR, SS](ca: Ctx[LL,RR])(f: RR => SS)(implicit functor: Functor[[R]Ctx[LL,R]])Ctx[LL,SS] <and>
// [Ctx[x, y] <: LeftBiased, LL, RR, MM](ca: Ctx[LL,RR])(f: LL => MM)(implicit functor: Functor[[L]Ctx[L,RR]])Ctx[MM,RR] <and>
// [Ctx[_], AA, BB](ca: Ctx[AA])(f: AA => BB)(implicit functor: Functor[Ctx])Ctx[BB]
// cannot be applied to (Either[String,Int])
// example(Right(3): Either[String, Int])
// ^
// example(Left("oops"): Either[String, Int])
// <console>:15: error: overloaded method value example with alternatives:
// [Ctx[x, y] <: RightBiased, LL, RR, SS](ca: Ctx[LL,RR])(f: RR => SS)(implicit functor: Functor[[R]Ctx[LL,R]])Ctx[LL,SS] <and>
// [Ctx[x, y] <: LeftBiased, LL, RR, MM](ca: Ctx[LL,RR])(f: LL => MM)(implicit functor: Functor[[L]Ctx[L,RR]])Ctx[MM,RR] <and>
// [Ctx[_], AA, BB](ca: Ctx[AA])(f: AA => BB)(implicit functor: Functor[Ctx])Ctx[BB]
// cannot be applied to (Either[String,Int])
// example(Left("oops"): Either[String, Int])
// ^
@vpatryshev
Copy link

Good trick; thanks. I'd disagree with naming the mapping on types as Context. Or is it already a widely-adopted term?

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