Skip to content

Instantly share code, notes, and snippets.

@mjhopkins
Last active August 29, 2015 14:27
Show Gist options
  • Save mjhopkins/1abd50f76145861bea23 to your computer and use it in GitHub Desktop.
Save mjhopkins/1abd50f76145861bea23 to your computer and use it in GitHub Desktop.
Experiments with typesafe apis
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