Last active
May 30, 2016 17:46
-
-
Save bvenners/7e23d79aee56c40ce045f0f062035dc0 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
: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]) | |
// ^ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Good trick; thanks. I'd disagree with naming the mapping on types as
Context
. Or is it already a widely-adopted term?