在 Scala 中使用 Shapeless 折叠不同类型的列表

2023-12-23

据我所知,无形提供了HList (异质性list)类型,可以包含多种类型。

可以折叠吗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)
    }
  )
}

但这不起作用,原因似乎与类型推断有关。不过,编写自定义类型类并不太难:

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不过,请按照您想要的方式输入别名。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Scala 中使用 Shapeless 折叠不同类型的列表 的相关文章

随机推荐