问题 Scala类型级编程 - 表示层次结构


我正在学习Scala中的类型级编程,我很好奇是否可以使用类型级编程来表示树或层次结构。

简单的情况是多级树

A_
  |
  B_
    |C
    |D
  |     
  E

如何表示这样的结构?


3345
2018-02-19 14:43


起源

除非我误解了你的问题,否则,树木是可能的。与Haskell这样的语言相比,我发现我不得不跳过更多的箍。 - Carcigenicate
大!我该怎么做..? :) - NightWolf
stackoverflow.com/q/27488849/3000206 当我第一次拿起Scala时,这是我的一个问题。我选择的答案有效,但就像我说的那样,它比其他语言复杂得多。 - Carcigenicate
你可能想查看Scalaz的树......这是一个例子: eed3si9n.com/learning-scalaz/Tree.html - Timothy Perrigo
是的我喜欢Scalaz树,但它的价值不是类型级别 - NightWolf


答案:


有许多方法可以在Scala中表示异构树,其中最简单的一种是这样的:

type MyTreeShape[A, B, C, D, E] = (A, (B, (C, D), E))

这有一些限制(尽管你不能将元组作为叶子的值,因为我们在表示中使用了元组)。对于这个答案的其余部分,我将使用稍微复杂的表示 无形的HList

import shapeless._

type MyTreeShape[A, B, C, D, E] =
  A ::
    (B ::
      (C :: HNil) ::
      (D :: HNil) ::
      HNil) ::
    (E :: HNil) ::
    HNil

这里有一棵树 HList 谁的头是价值,谁的尾巴是 HList 儿童树。

如果我们想对这些树进行有用的通用操作,我们需要一些类型类。我会写一个快速深度优先 FlattenTree 关于Shapeless的类型类的模型 ops.hlist 包作为例子。可以类似地实现尺寸,深度等的其他类型类。

这是类型类和方便的方法,使它易于使用:

trait FlattenTree[T <: HList] extends DepFn1[T] { type Out <: HList }

def flattenTree[T <: HList](t: T)(implicit f: FlattenTree[T]): f.Out = f(t)

现在我们将放入伴随对象的实例:

object FlattenTree {
  type Aux[T <: HList, Out0 <: HList] = FlattenTree[T] { type Out = Out0 }

  implicit def flattenTree[H, T <: HList](implicit
    tf: FlattenForest[T]
  ): Aux[H :: T, H :: tf.Out] = new FlattenTree[H :: T] {
    type Out = H :: tf.Out

    def apply(t: H :: T): H :: tf.Out = t.head :: tf(t.tail)
  }
}

请注意,这需要一个帮助器类型, FlattenForest

trait FlattenForest[F <: HList] extends DepFn1[F] { type Out <: HList }

object FlattenForest {
  type Aux[F <: HList, Out0 <: HList] = FlattenForest[F] { type Out = Out0 }

  implicit val hnilFlattenForest: Aux[HNil, HNil] = new FlattenForest[HNil] {
    type Out = HNil

    def apply(f: HNil): HNil = HNil
  }

  implicit def hconsFlattenForest[
    H <: HList,
    OutH <: HList,
    T <: HList,
    OutT <: HList
  ](implicit
    hf: FlattenTree.Aux[H, OutH],
    tf: Aux[T, OutT],
    pp: ops.hlist.Prepend[OutH, OutT]
  ): Aux[H :: T, pp.Out] = new FlattenForest[H :: T] {
    type Out = pp.Out

    def apply(f: H :: T): pp.Out = pp(hf(f.head), tf(f.tail))
  }
}

现在我们可以像这样使用它:

val myTree: MyTreeShape[String, Int, Char, Symbol, Double] =
  "foo" :: (10 :: HList('a') :: HList('z) :: HNil) :: HList(0.0) :: HNil

val flattened = flattenTree(myTree)

让我们看一下静态类型是否合适:

flattened: String :: Int :: Char :: Symbol :: Double :: HNil

而这正是我们想要的。

你可以在没有Shapeless的情况下做到这一切,但它会涉及到令人难以置信的样板量。


14
2018-03-03 00:29



真棒的答案! - NightWolf