Ai fini di un esempio di lavoro completo, supponiamo abbiamo alcune semplici algebre:
sealed trait AuthOp[A]
case class Login(user: String, pass: String) extends AuthOp[Option[String]]
case class HasPermission(user: String, access: String) extends AuthOp[Boolean]
sealed trait InteractOp[A]
case class Ask(prompt: String) extends InteractOp[String]
case class Tell(msg: String) extends InteractOp[Unit]
sealed trait LogOp[A]
case class Record(msg: String) extends LogOp[Unit]
E alcuni interpreti (senza senso, ma in fase di compilazione in grado):
import scalaz.~>, scalaz.Id.Id
val AuthInterpreter: AuthOp ~> Id = new (AuthOp ~> Id) {
def apply[A](op: AuthOp[A]): A = op match {
case Login("foo", "bar") => Some("foo")
case Login(_, _) => None
case HasPermission("foo", "any") => true
case HasPermission(_, _) => false
}
}
val InteractInterpreter: InteractOp ~> Id = new (InteractOp ~> Id) {
def apply[A](op: InteractOp[A]): A = op match {
case Ask(p) => p
case Tell(_) =>()
}
}
val LogInterpreter: LogOp ~> Id = new (LogOp ~> Id) {
def apply[A](op: LogOp[A]): A = op match {
case Record(_) =>()
}
}
A questo punto dovresti essere in grado di piegare uno HList
di interpreti come questo:
import scalaz.Coproduct
import shapeless.Poly2
object combine extends Poly2 {
implicit def or[F[_], G[_], H[_]]: Case.Aux[
F ~> H,
G ~> H,
({ type L[x] = Coproduct[F, G, x] })#L ~> H
] = at((f, g) =>
new (({ type L[x] = Coproduct[F, G, x] })#L ~> H) {
def apply[A](fa: Coproduct[F, G, A]): H[A] = fa.run.fold(f, g)
}
)
}
Ma questo non funziona per ragioni che sembrano avere qualcosa a che fare con l'inferenza di tipo. Non è troppo difficile da scrivere una classe tipo personalizzato, però:
import scalaz.Coproduct
import shapeless.{ DepFn1, HList, HNil, :: }
trait Interpreters[L <: HList] extends DepFn1[L]
object Interpreters {
type Aux[L <: HList, Out0] = Interpreters[L] { type Out = Out0 }
implicit def interpreters0[F[_], H[_]]: Aux[(F ~> H) :: HNil, F ~> H] =
new Interpreters[(F ~> H) :: HNil] {
type Out = F ~> H
def apply(in: (F ~> H) :: HNil): F ~> H = in.head
}
implicit def interpreters1[F[_], G[_], H[_], T <: HList](implicit
ti: Aux[T, G ~> H]
): Aux[(F ~> H) :: T, ({ type L[x] = Coproduct[F, G, x] })#L ~> H] =
new Interpreters[(F ~> H) :: T] {
type Out = ({ type L[x] = Coproduct[F, G, x] })#L ~> H
def apply(
in: (F ~> H) :: T
): ({ type L[x] = Coproduct[F, G, x] })#L ~> H =
new (({ type L[x] = Coproduct[F, G, x] })#L ~> H) {
def apply[A](fa: Coproduct[F, G, A]): H[A] =
fa.run.fold(in.head, ti(in.tail))
}
}
}
E poi è possibile scrivere il combine
:
def combine[L <: HList](l: L)(implicit is: Interpreters[L]): is.Out = is(l)
e usarlo:
type Language0[A] = Coproduct[InteractOp, AuthOp, A]
type Language[A] = Coproduct[LogOp, Language0, A]
val interpreter: Language ~> Id =
combine(LogInterpreter :: InteractInterpreter :: AuthInterpreter :: HNil)
Potreste essere in grado per ottenere la versione Poly2
funzionante, ma questa classe di tipo sarebbe probabilmente abbastanza semplice per me. Sfortunatamente, non sarai in grado di semplificare la definizione dell'alias di tipo Language
nel modo desiderato.
È possibile piegare un 'HList' utilizzando una funzione polimorfica. Per favore, fai riferimento alla [guida informe] (https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#heterogenous-lists). –