For a complete working example, suppose we have simple algebras:
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]
And some (meaningless, but compiled) interpreters:
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(_) => () } }
At this point, you can reset the HList interpreters as follows:
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) } ) }
But this does not work for reasons that seem to be related to the type of output. It is not too difficult to write your own type class, but:
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)) } } }
And then you can write your combine :
def combine[L <: HList](l: L)(implicit is: Interpreters[L]): is.Out = is(l)
And use it:
type Language0[A] = Coproduct[InteractOp, AuthOp, A] type Language[A] = Coproduct[LogOp, Language0, A] val interpreter: Language ~> Id = combine(LogInterpreter :: InteractInterpreter :: AuthInterpreter :: HNil)
You might be able to get the Poly2 version, but a class of this type would probably be simple enough for me. Unfortunately, you cannot simplify the definition of an alias of type Language way you want.