在 Scala 中使用 Shapeless 折叠不同类型的列表 [英] Folding a list of different types using Shapeless in Scala

查看:44
本文介绍了在 Scala 中使用 Shapeless 折叠不同类型的列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

据我所知,shapeless 提供了 HList(异构 列表)类型,它可以包含多种类型.

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

是否可以折叠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的生​​成吗?

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]

推荐答案

为了一个完整的工作示例,假设我们有一些简单的代数:

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(_) => ()
  }
}

此时你应该能够像这样折叠HList解释器:

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

然后你可以编写你的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 类型别名的定义.

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.

这篇关于在 Scala 中使用 Shapeless 折叠不同类型的列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆