【问题标题】:Fold over HList with unknown Types折叠具有未知类型的 HList
【发布时间】:2016-02-01 21:39:15
【问题描述】:

我有一种复杂的类型层次结构,但要分解它有两个基本特征:ConvertableConversion[A <: Convertable, B <: Convertable,例如有一个转换可以将 Mealy 自动机转换为 Moore 自动机。 每个Conversion[A,B] 都有一个convert(automaton: A) : B 方法。

现在我要介绍smart Conversions的概念,它基本上是一个正常转换的List,会一个接一个地执行。 因此我引入了一个AutoConversion trait,扩展了一个具有val path : HList 参数的转换来表示转换链,并且应该实现convert 方法,这样AutoConversions 只需提供实际转换的列表采取。 我认为您可以在path 上使用fold 来实现这一点,所以这是我的第一次尝试:

package de.uni_luebeck.isp.conversions

import shapeless._
import shapeless.ops.hlist.LeftFolder

trait AutoConversion[A <: Convertable, B <: Convertable] extends Conversion[A, B] {
  val path: HList

  object combiner extends Poly {
      implicit def doSmth[C <: Convertable, D <: Convertable] = 
         use((conv : Conversion[C, D] , automaton : C) => conv.convert(automaton))

}

  override def convert(startAutomaton: A): B = {
    path.foldLeft(startAutomaton)(combiner)
  }
}

这行不通,因为找不到隐式文件夹,所以我猜我必须在某个地方为编译器提供更多类型信息,但不知道在哪里

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    您需要更多类型信息是对的,一般来说,如果您有 HList 作为静态类型的值,您可能需要更改方法。如果您只知道 HList(除了为其添加值),那么您基本上对 HList 无能为力,并且您通常只会将 HList 作为类型约束编写。

    在您的情况下,您所描述的是一种类型对齐的序列。在您继续使用这种方法之前,我建议您确定您确实需要这样做。函数(以及类似函数的类型,如 Conversion)的优点之一是它们可以组合:你有一个 A =&gt; B 和一个 B =&gt; C,你将它们组合成一个 A =&gt; C 并且可以忘记 @987654329 @ 永远。你会得到一个干净整洁的黑匣子,这通常正是你想要的。

    不过,在某些情况下,能够以一种可以反映管道各个部分的方式组合类似函数的事物会很有用。我将假设这是其中一种情况,但您应该自己确认。如果不是,那你很幸运,因为接下来的事情有点混乱。

    我会假设这些类型:

    trait Convertable
    
    trait Conversion[A <: Convertable, B <: Convertable] {
      def convert(a: A): B
    }
    

    我们可以定义一个类型类来证明特定的HList 由一个或多个类型排列的转换组成:

    import shapeless._
    
    trait TypeAligned[L <: HList] extends DepFn1[L] {
      type I <: Convertable
      type O <: Convertable
      type Out = Conversion[I, O]
    }
    

    L 包含有关管道的所有类型信息,IO 是其端点的类型。

    接下来我们需要这个类型类的实例(请注意,这必须与上面的 trait 一起定义,以便两者相伴):

    object TypeAligned {
      type Aux[L <: HList, A <: Convertable, B <: Convertable] = TypeAligned[L] {
        type I = A
        type O = B
      }
    
      implicit def firstTypeAligned[
        A <: Convertable,
        B <: Convertable
      ]: TypeAligned.Aux[Conversion[A, B] :: HNil, A, B] =
        new TypeAligned[Conversion[A, B] :: HNil] {
          type I = A
          type O = B
          def apply(l: Conversion[A, B] :: HNil): Conversion[A, B] = l.head
        }
    
      implicit def composedTypeAligned[
        A <: Convertable,
        B <: Convertable,
        C <: Convertable,
        T <: HList
      ](implicit
        tta: TypeAligned.Aux[T, B, C]
      ): TypeAligned.Aux[Conversion[A, B] :: T, A, C] =
        new TypeAligned[Conversion[A, B] :: T] {
          type I = A
          type O = C
          def apply(l: Conversion[A, B] :: T): Conversion[A, C] =
            new Conversion[A, C] {
              def convert(a: A): C = tta(l.tail).convert(l.head.convert(a))
            }
        }
    }
    

    现在您可以编写一个版本的AutoConversion 来跟踪有关管道的所有类型信息:

    class AutoConversion[L <: HList, A <: Convertable, B <: Convertable](
      path: L
    )(implicit ta: TypeAligned.Aux[L, A, B]) extends Conversion[A, B] {
      def convert(a: A): B = ta(path).convert(a)
    }
    

    你可以这样使用它:

    case class AutoA(i: Int) extends Convertable
    case class AutoB(s: String) extends Convertable
    case class AutoC(c: Char) extends Convertable
    
    val ab: Conversion[AutoA, AutoB] = new Conversion[AutoA, AutoB] {
      def convert(a: AutoA): AutoB = AutoB(a.i.toString)
    }
    
    val bc: Conversion[AutoB, AutoC] = new Conversion[AutoB, AutoC] {
      def convert(b: AutoB): AutoC = AutoC(b.s.lift(3).getOrElse('-'))
    }
    
    val conv = new AutoConversion(ab :: bc :: HNil)
    

    conv 将具有预期的静态类型(并实现Conversion[AutoA, AutoC])。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-10-29
      • 2020-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-24
      • 1970-01-01
      • 2021-01-08
      相关资源
      最近更新 更多