Last active
August 29, 2015 14:27
-
-
Save mjhopkins/1abd50f76145861bea23 to your computer and use it in GitHub Desktop.
Experiments with typesafe apis
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 api | |
import shapeless.{Succ, _0, Nat} | |
import shapeless.nat._2 | |
trait BasicDefinitions { | |
// type level booleans | |
sealed trait Flag | |
trait On extends Flag | |
trait Off extends Flag | |
trait Option[+T] | |
// scala standard library unfortunately doesn't expose a None type | |
case class Some[+T](value: T) extends Option[T] { | |
def get: T = value | |
} | |
trait None extends Option[Nothing] | |
case object None extends None | |
} | |
object Options extends BasicDefinitions { | |
case class Opt[InMemory <: Flag, Reducers <: Option[Int], Unbalanced <: Option[Int], PreStepReducers <: Option[Int]] private[Options]( | |
reducers: Reducers, | |
unbalanced: Unbalanced, | |
preStepReducers: PreStepReducers | |
) { | |
def |[D](f: Opt[InMemory, Reducers, Unbalanced, PreStepReducers] => D): D = f(this) | |
} | |
val Empty: Opt[Off, None, None, None] = Opt[Off, None, None, None](None, None, None) | |
def InMemory[Reducers <: Option[Int], Unbalanced <: Option[Int], PreStepReducers <: Option[Int]] = | |
(o: Opt[Off, Reducers, Unbalanced, PreStepReducers]) => | |
o.copy[On, Reducers, Unbalanced, PreStepReducers]() | |
def Reducers[InMemory <: Flag, Unbalanced <: Option[Int], PreStepReducers <: Option[Int]](reducers: Int) = | |
(o: Opt[InMemory, None, Unbalanced, PreStepReducers]) => | |
o.copy[InMemory, Some[Int], Unbalanced, PreStepReducers](reducers = Some(reducers)) | |
def Unbalanced[InMemory <: Flag, Reducers <: Option[Int], PreStepReducers <: Option[Int]](unbalanced: Int) = | |
(o: Opt[InMemory, Reducers, None, PreStepReducers]) => | |
o.copy[InMemory, Reducers, Some[Int], PreStepReducers](unbalanced = Some(unbalanced)) | |
def PreStepReducers[InMemory <: Flag, Reducers <: Option[Int], Unbalanced <: Option[Int]](preStepReducers: Int) = | |
(o: Opt[InMemory, Reducers, Unbalanced, None]) => | |
o.copy[InMemory, Reducers, Unbalanced, Some[Int]](preStepReducers = Some(preStepReducers)) | |
} | |
object TestOpt { | |
import Options._ | |
def takesInMemoryAndReducers[U <: Option[Int], P <: Option[Int]](opt: Opt[On, Some[Int], U, P]): Int = { | |
val Some(reducers) = opt.reducers | |
reducers | |
} | |
// Empty | InMemory | InMemory // Compilation error -- InMemory can't be turned on twice | |
// takesInMemoryAndReducers(Empty | Reducers(12)) // compilation error | |
takesInMemoryAndReducers(Empty | Reducers(12) | InMemory) // OK | |
takesInMemoryAndReducers(Empty | InMemory | Reducers(12)) | |
// OK | |
def mustBeNotInMemoryAndUnbalanced[R <: Some[Int], P <: Some[Int]](opt: Opt[Off, R, Some[Int], P]) = 4 | |
// mustBeNotInMemoryAndUnbalanced(Empty | InMemory ) // compilation error | |
// mustBeNotInMemoryAndUnbalanced(Empty | InMemory | Unbalanced(15)) // compilation error | |
// mustBeNotInMemoryAndUnbalanced(Empty | Unbalanced(15) | InMemory) // compilation error | |
} | |
object Options2 extends BasicDefinitions { | |
/* | |
Here, we enforce that an Opt2 cannot even be constructed with more than one of A, B, or C provided. | |
*/ | |
case class Opt2[A <: Option[Int], B <: Option[Int], C <: Option[Int], Done <: Flag] private[Options2](a: A, b: B, c: C) { | |
def |[Z](f: Opt2[A, B, C, Done] => Z): Z = f(this) | |
} | |
val Empty2: Opt2[None, None, None, Off] = Opt2[None, None, None, Off](None, None, None) | |
def A(a: Int) = (o: Opt2[None, None, None, Off]) => Opt2[Some[Int], None, None, On](Some(a), None, None) | |
def B(b: Int) = (o: Opt2[None, None, None, Off]) => Opt2[None, Some[Int], None, On](None, Some(b), None) | |
def C(c: Int) = (o: Opt2[None, None, None, Off]) => Opt2[None, None, Some[Int], On](None, None, Some(c)) | |
} | |
object TestOpt2 { | |
import Options2._ | |
def oneOfTheThreeMustBeTurnedOn[A <: Option[Int], B <: Option[Int], C <: Option[Int]](opt: Opt2[A, B, C, On]) = { | |
// in this form it is a little hard to get the value out, because the choice of A vs B vs C is not reified | |
val value: Int = (opt.a, opt.b, opt.c) match { | |
case (Some(a), None, None) => a | |
case (None, Some(b), None) => b | |
case (None, None, Some(c)) => c | |
} | |
} | |
} | |
object Options3 extends BasicDefinitions { | |
/* | |
let's keep a count of how many options we have applied | |
*/ | |
case class Opt3[A <: Option[Int], B <: Option[Int], C <: Option[Int], Count <: Nat] private[Options3](a: A, b: B, c: C) { | |
def |[Z](f: Opt3[A, B, C, Count] => Z): Z = f(this) | |
} | |
def Empty = Opt3[None, None, None, _0](None, None, None) | |
def A[B <: Option[Int], C <: Option[Int], Count <: Nat](a: Int) = (o: Opt3[None, B, C, Count]) => o.copy[Some[Int], B, C, Succ[Count]](a = Some(a)) | |
def B[A <: Option[Int], C <: Option[Int], Count <: Nat](b: Int) = (o: Opt3[A, None, C, Count]) => o.copy[A, Some[Int], C, Succ[Count]](b = Some(b)) | |
def C[A <: Option[Int], B <: Option[Int], Count <: Nat](c: Int) = (o: Opt3[A, B, None, Count]) => o.copy[A, B, Some[Int], Succ[Count]](c = Some(c)) | |
} | |
object TestOpt3 { | |
import Options3._ | |
/* | |
exactly 2 out of 3 must be on | |
*/ | |
def use[A <: Option[Int], B <: Option[Int], C <: Option[Int]](o: Opt3[A, B, C, _2]) = ??? | |
/* | |
at least 2 out of 3 must be on | |
*/ | |
def use2[A <: Option[Int], B <: Option[Int], C <: Option[Int], count <: Nat](o: Opt3[A, B, C, Succ[Succ[count]]]) = ??? | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment