2015-11-28 8 views
7

Come noto, la forma senza forma fornisce il tipo HList (Eterogeno) che può includere più tipi.Piegare un elenco di tipi diversi utilizzando Shapeless in Scala

È possibile piegare HList? per esempio,

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

Anche, posso semplificare generazione di 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

È 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). –

risposta

7

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.

+0

Grazie per il bell'esempio di lavoro. – 1ambda

Problemi correlati