Folding a list of different types using Shapeless in Scala

爷,独闯天下 提交于 2019-12-31 10:51:37

问题


As I known, shapeless provides the HList (Heterogenous list) type which can include multiple types.

Is it possible to fold HList? for example,

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

Even, can I simplify generation of 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]

回答1:


For the sake of a complete working example, suppose we've got some 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 (pointless but compile-able) 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 should be able to fold over an HList of interpreters like this:

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 that doesn't work for reasons that seem to have something to do with type inference. It's not too hard to write a custom type class, though:

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 the Poly2 version working, but this type class would probably be straightforward enough for me. Unfortunately you're not going to be able to simplify the definition of the Language type alias in the way you want, though.



来源:https://stackoverflow.com/questions/33971404/folding-a-list-of-different-types-using-shapeless-in-scala

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!