2015-11-28 66 views
7

正如我已知使用无形不同类型的列表,不成形提供HList异构列表)类型,其可以包括多个类型。折叠Scala中

是否可以折叠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] 
+2

您可以使用多态函数折叠'HList'。请参阅[无形指南](https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#heterogenous-lists)。 –

回答

7

对于一个完整的工作示例起见,假设我们有一些简单的代数:

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) 
    } 
) 
} 

但是这不起作用的原因似乎与类型推理有关。这不是太难写一个自定义类型的类,但:

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版本的工作,但这种类型可能对我来说足够简单。不幸的是,你不能以你想要的方式简化Language类型别名的定义。

+0

谢谢你这个漂亮的工作例子。 – 1ambda