Last active
September 21, 2017 01:57
-
-
Save logicalguess/a104fffad4c0213363d562a85e85d198 to your computer and use it in GitHub Desktop.
type safe partial function union/merge with compile time verification
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 logicalguess.receiver | |
import scala.reflect.ClassTag | |
case class Call[U <: Union](pf: PartialFunction[Any, Any]) { | |
import Call._ | |
def union[In](f: Function[In, Any])(implicit ct: ClassTag[In]) = Call[U | In](pf.orElse(downcast(f))) | |
def apply[In](in: In)(implicit ev: In isPartOf U) = pf(in) | |
} | |
object Call { | |
def empty = | |
Call[Nothing | Nothing](emptyFunction) | |
def union[A](f: PartialFunction[A, Any])(implicit ct: ClassTag[A]) = | |
Call[Nothing | A](emptyFunction.orElse(downcast(f))) | |
def downcast[In](f: In => Any)(implicit ct: ClassTag[In]): PartialFunction[Any, Any] = { | |
case (in: In) => f(in) | |
} | |
} | |
object emptyFunction extends PartialFunction[Any, Any] { | |
def isDefinedAt(x: Any) = false | |
def apply(x: Any) = throw new UnsupportedOperationException("Empty function") | |
} | |
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 logicalguess.receiver | |
import scala.reflect.ClassTag | |
trait CompositeReceiver extends Receiver[Any] { | |
type O = Any | |
trait TypeHolder { | |
type U <: Union | |
val call: Call[U] | |
} | |
var holder: TypeHolder = new TypeHolder { | |
override type U = Nothing | Nothing | |
override val call: Call[|[Nothing, Nothing]] = Call.empty | |
} | |
//var pf: PartialFunction[Any, Any] = emptyFunction | |
def receiver[I: ClassTag](next: PartialFunction[I, Any]) { | |
val h = holder | |
holder = new TypeHolder { | |
type U = h.U | I | |
val call = h.call.union[I](next) | |
} | |
//pf = pf.orElse(Call.downcast(next)) | |
} | |
def receive(in: Any): Any = holder.call.pf(in) //can't use call directly... | |
} | |
object Example { | |
val pfs: PartialFunction[String, Any] = { | |
case s: String => s + s | |
} | |
val pfi: PartialFunction[Int, Any] = { | |
case i: Int => i*i | |
} | |
def main(args: Array[String]): Unit = { | |
trait StringReceiver { this: CompositeReceiver => | |
receiver { pfs } | |
} | |
trait IntReceiver { this: CompositeReceiver => | |
receiver { pfi } | |
} | |
object receiver extends CompositeReceiver with StringReceiver with IntReceiver | |
println(receiver.receive("abc")) | |
println(receiver.receive(7)) | |
} | |
} |
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 logicalguess.receiver | |
import scala.reflect.ClassTag | |
import scala.language.implicitConversions | |
trait Receiver[I] { | |
type O | |
def receive(in: I): O | |
} | |
object Receiver { | |
def from[In, Out](pf: PartialFunction[In, Out]): Receiver[In] = new Receiver[In] { | |
type O = Out | |
override def receive(in: In): Out = pf(in) | |
} | |
implicit def receiver2function[In](r: Receiver[In])(implicit ct: ClassTag[In]): PartialFunction[In, Any] = { | |
case in: In => r.receive(in) | |
} | |
} | |
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 logicalguess.receiver | |
/** | |
* http://blog.knutwalker.de/typed-actors/tut/union.html | |
*/ | |
import scala.annotation.implicitNotFound | |
sealed trait Union | |
sealed trait |[+A, +B] extends Union | |
@implicitNotFound("Cannot prove that ${A} is a union type.") | |
sealed trait IsUnion[-A] { | |
type Out <: Union | |
} | |
object IsUnion { | |
type Aux[-A0, U0 <: Union] = IsUnion[A0] {type Out = U0} | |
implicit def isUnion[A <: Union]: Aux[A, A] = | |
new IsUnion[A] { | |
type Out = A | |
} | |
} | |
@implicitNotFound("Cannot prove that message of type ${A} is a member of ${U}.") | |
sealed trait isPartOf[A, +U <: Union] | |
object isPartOf extends IsPartOf0 { | |
implicit def leftPart[A](implicit ev: A isNotA Union): isPartOf[A, A | Nothing] = | |
null | |
implicit def rightPart[A](implicit ev: A isNotA Union): isPartOf[A, Nothing | A] = | |
null | |
} | |
sealed trait IsPartOf0 { | |
implicit def tailPart1[A, U <: Union](implicit partOfTl: A isPartOf U): isPartOf[A, U | Nothing] = | |
null | |
implicit def tailPart2[A, U <: Union](implicit partOfTl: A isPartOf U): isPartOf[A, Nothing | U] = | |
null | |
} | |
// @annotation.implicitAmbiguous("${A} must not be <: ${B}") | |
sealed trait isNotA[A, B] | |
object isNotA { | |
implicit def nsub[A, B]: A isNotA B = null | |
// $COVERAGE-OFF$Code only exists to prove non-equality and is expected to never execute | |
implicit def nsubAmbig1[A, B >: A]: A isNotA B = sys.error("Unexpected invocation") | |
implicit def nsubAmbig2[A, B >: A]: A isNotA B = sys.error("Unexpected invocation") | |
// $COVERAGE-ON$ | |
} | |
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 logicalguess.receiver | |
object UnionExample { | |
sealed trait InputA | |
case class IntA(val i: Int) extends InputA | |
case class BoolA(val b: Boolean) extends InputA | |
sealed trait InputB | |
case class StringB(val s: String) extends InputB | |
case class IntB(val i: Int) extends InputB | |
case class Bool(val b: Boolean) | |
val pfa: PartialFunction[InputA, Any] = { | |
case IntA(i) => i | |
case BoolA(b) => b | |
} | |
val pfb: PartialFunction[InputB, Any] = { | |
case StringB(s) => s | |
case IntB(i) => i | |
} | |
val pfc: PartialFunction[Bool, Any] = { | |
case Bool(b) => b | |
} | |
import Call._ | |
val pf = downcast(pfa).orElse(downcast(pfb)) | |
val ra: Receiver[InputA] = Receiver.from(pfa) | |
val rb: Receiver[InputB] = Receiver.from(pfb) | |
val rc: Receiver[Bool] = Receiver.from(pfc) | |
def main(args: Array[String]): Unit = { | |
println("function call IntA: " + pf(IntA(55))) | |
println("function call BoolA: " + pf(BoolA(true))) | |
println("function call StringB: " + pf(StringB("abc"))) | |
println("function call IntB: " + pf(IntB(77))) | |
//println(pf(Bool(true))) // runtime error: MatchError | |
type Input = InputA | InputB | |
//val call = Call[Input](pf) | |
val call = Call.empty.union(pfa).union(pfb) | |
println("evidence call IntA: " + call(IntA(55))) | |
println("evidence call BoolA: " + call(BoolA(true))) | |
println("evidence call StringB: " + call(StringB("abc"))) | |
println("evidence call IntB: " + call(IntB(77))) | |
//call(Bool(true)) //compile error | |
//type Inputs = |[|[InputA, InputB], Bool] | |
//println(Call[Input | Bool](downcast(pfa).orElse(downcast(pfb)).orElse(downcast(pfc)))(Bool(false))) | |
val chain: Call[|[|[|[Nothing, InputA], InputB], Bool]] = Call.union(pfa).union(pfb).union(pfc) | |
println(chain(Bool(false))) | |
println(chain(IntA(99))) | |
println(chain(StringB("xyz"))) | |
val rchain = Call.empty.union(ra).union(rb).union(rc) | |
println(rchain(Bool(false))) | |
println(rchain(IntA(99))) | |
println(rchain(StringB("xyz"))) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment