-
-
Save vil1/073c1db241dcb4a6ca6c5992c0c57a3e to your computer and use it in GitHub Desktop.
A type level state machine, whose definition is statically checked to be deterministic (material related to http://kanaka.io/blog/2016/04/25/interlude-typelevel-state-machine.html)
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
import shapeless._, labelled._, ops.record._ | |
import scala.annotation.implicitNotFound | |
import scala.language.dynamics | |
class Transition[In, Out] | |
class Definition[R <: HList] | |
class StateMachine[S] extends Dynamic { | |
def selectDynamic[R <: HList, O, T](trans: Witness) | |
(implicit d: Definition[R], | |
select: Selector.Aux[R, trans.T, T], | |
unpack: Unpack2[T, Transition, S, O] | |
): StateMachine[O] = new StateMachine[O] | |
} | |
object StateMachine { | |
def initial[S] = new StateMachine[S] | |
def definition[R <: HList : IsDeterministic](r: R) = new Definition[R] | |
def transition[I, O] = new Transition[I, O] | |
} | |
// Typeclass witnessing that if F is of shape FieldType[K, Transition[I, O1]] | |
// there is no field of type FieldType[K, Transition[I, O2]] in record R | |
class IsNotMemberOf[F, R <: HList] | |
object IsNotMemberOf { | |
def apply[F, L <: HList](f: F, l: L)(implicit isNotMemberOf: IsNotMemberOf[F, L]) = true | |
implicit def hnilIsUnique[F] = new IsNotMemberOf[F, HNil] | |
implicit def hconsIsUnique1[K, I, O, H, T <: HList, In, Out] | |
(implicit unpackV: Unpack2[H, Transition, In, Out], | |
ev : In =:!= I, | |
notMemberOfTail: IsNotMemberOf[FieldType[K, Transition[I, O]], T] | |
) = new IsNotMemberOf[FieldType[K, Transition[I, O]], FieldType[K, H] :: T] | |
implicit def hconsIsUnique2[K,V, K2, H, T <: HList] | |
(implicit ev : K =:!= K2, | |
notMemberOfTail: IsNotMemberOf[FieldType[K, V], T] | |
) = new IsNotMemberOf[FieldType[K, V], FieldType[K2, H] :: T] | |
} | |
@implicitNotFound(msg = "The provided definition is not deterministic, " + | |
"it contains at least two transitions with the same label starting from the same state") | |
class IsDeterministic[R <: HList] | |
object IsDeterministic { | |
implicit def hnilIsDeterministic = new IsDeterministic[HNil] | |
implicit def hconsIsDeterministic[H, T <: HList] | |
(implicit headIsNotMemberOfTail: IsNotMemberOf[H, T], | |
tailIsDeterministic : IsDeterministic[T] | |
) = new IsDeterministic[H :: T] | |
} | |
object Door { | |
import StateMachine._ | |
import syntax.singleton._ | |
trait Open | |
trait Closed | |
trait Locked | |
implicit val transitions = definition( | |
"open" ->> transition[Closed, Open] :: | |
"close" ->> transition[Open, Closed] :: | |
"lock" ->> transition[Closed, Locked] :: | |
"unlock" ->> transition[Locked, Closed] :: | |
HNil | |
) | |
// non-deterministic definition, doesn't compile | |
/* | |
implicit val transitions = Definition( | |
("open" ->> Transition[Closed, Open]) :: | |
("close" ->> Transition[Open, Closed]) :: | |
("lock" ->> Transition[Closed, Locked]) :: | |
("unlock" ->> Transition[Locked, Closed]) :: | |
("unlock" ->> Transition[Locked, Open]) :: | |
HNil | |
) | |
*/ | |
val init = initial[Open] | |
// compiles | |
init.close.lock.unlock.open | |
// doesn't compile | |
// init.open | |
// init.close.close | |
// init.close.lock.open | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment