在 Scala 中使用 Shapeless 折叠不同类型的列表
Folding a list of different types using Shapeless in Scala
据我所知,shapeless 提供了 HList
(Heterogenous 列表) 类型,可以包含多种类型。
是否可以折叠HList
?例如,
// ref - Composable application architecture with reasonably priced monad
// code - https://github.com/stew/reasonably-priced/blob/master/src/main/scala/reasonable/App.scala
import scalaz.{Coproduct, Free, Id, NaturalTransformation}
def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): ({type cp[α] = Coproduct[F,G,α]})#cp ~> H =
new NaturalTransformation[({type cp[α] = Coproduct[F,G,α]})#cp,H] {
def apply[A](fa: Coproduct[F,G,A]): H[A] = fa.run match {
case -\/(ff) ⇒ f(ff)
case \/-(gg) ⇒ g(gg)
}
}
type Language0[A] = Coproduct[InteractOp, AuthOp, A]
type Language[A] = Coproduct[LogOp, Language0, A]
val interpreter0: Language0 ~> Id = or(InteractInterpreter, AuthInterpreter)
val interpreter: Language ~> Id = or(LogInterpreter, interpreter0)
// What if we have `combine` function which folds HList
val interpreters: Language ~> Id = combine(InteractInterpreter :: AuthInterpreter :: LoginInterpreter :: HNil)
甚至,我可以简化Langauge
的生成吗?
type Language0[A] = Coproduct[InteractOp, AuthOp, A]
type Language[A] = Coproduct[LogOp, Language0, A]
// What if we can create `Language` in one line
type Language[A] = GenCoproduct[InteractOp, AuthOp, LogOp, A]
为了一个完整的工作示例,假设我们有一些简单的代数:
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]
还有一些(无意义但可编译的)解释器:
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(_) => ()
}
}
在这一点上,你应该能够像这样折叠 HList
个口译员:
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)
}
)
}
但由于似乎与类型推断有关的原因,这不起作用。编写自定义类型并不难 class,不过:
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))
}
}
}
然后你可以写你的 combine
:
def combine[L <: HList](l: L)(implicit is: Interpreters[L]): is.Out = is(l)
并使用它:
type Language0[A] = Coproduct[InteractOp, AuthOp, A]
type Language[A] = Coproduct[LogOp, Language0, A]
val interpreter: Language ~> Id =
combine(LogInterpreter :: InteractInterpreter :: AuthInterpreter :: HNil)
您可能能够使 Poly2
版本正常工作,但这种类型 class 对我来说可能已经足够简单了。不幸的是,您无法按照您想要的方式简化 Language
类型别名的定义。
据我所知,shapeless 提供了 HList
(Heterogenous 列表) 类型,可以包含多种类型。
是否可以折叠HList
?例如,
// ref - Composable application architecture with reasonably priced monad
// code - https://github.com/stew/reasonably-priced/blob/master/src/main/scala/reasonable/App.scala
import scalaz.{Coproduct, Free, Id, NaturalTransformation}
def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): ({type cp[α] = Coproduct[F,G,α]})#cp ~> H =
new NaturalTransformation[({type cp[α] = Coproduct[F,G,α]})#cp,H] {
def apply[A](fa: Coproduct[F,G,A]): H[A] = fa.run match {
case -\/(ff) ⇒ f(ff)
case \/-(gg) ⇒ g(gg)
}
}
type Language0[A] = Coproduct[InteractOp, AuthOp, A]
type Language[A] = Coproduct[LogOp, Language0, A]
val interpreter0: Language0 ~> Id = or(InteractInterpreter, AuthInterpreter)
val interpreter: Language ~> Id = or(LogInterpreter, interpreter0)
// What if we have `combine` function which folds HList
val interpreters: Language ~> Id = combine(InteractInterpreter :: AuthInterpreter :: LoginInterpreter :: HNil)
甚至,我可以简化Langauge
的生成吗?
type Language0[A] = Coproduct[InteractOp, AuthOp, A]
type Language[A] = Coproduct[LogOp, Language0, A]
// What if we can create `Language` in one line
type Language[A] = GenCoproduct[InteractOp, AuthOp, LogOp, A]
为了一个完整的工作示例,假设我们有一些简单的代数:
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]
还有一些(无意义但可编译的)解释器:
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(_) => ()
}
}
在这一点上,你应该能够像这样折叠 HList
个口译员:
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)
}
)
}
但由于似乎与类型推断有关的原因,这不起作用。编写自定义类型并不难 class,不过:
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))
}
}
}
然后你可以写你的 combine
:
def combine[L <: HList](l: L)(implicit is: Interpreters[L]): is.Out = is(l)
并使用它:
type Language0[A] = Coproduct[InteractOp, AuthOp, A]
type Language[A] = Coproduct[LogOp, Language0, A]
val interpreter: Language ~> Id =
combine(LogInterpreter :: InteractInterpreter :: AuthInterpreter :: HNil)
您可能能够使 Poly2
版本正常工作,但这种类型 class 对我来说可能已经足够简单了。不幸的是,您无法按照您想要的方式简化 Language
类型别名的定义。