【问题标题】:Modify something in a Scala tree修改 Scala 树中的某些内容
【发布时间】:2019-04-16 00:33:46
【问题描述】:

我在 Scala 中有一个使用案例类构建的树数据结构(它属于 AST,但这与问题无关)。为了修改树,我使用了一个递归函数来破坏和重建整个树中的每个案例类:

def update(tree: Tree, id: String, val: Int): Tree = {
  tree match {
    case NodeType1(childs) =>
      NodeType1(childs.map(update(_, id, val)))
    case NodeType2(a, childs) =>
      NodeType2(a, childs.map(update(_, id, val))
    case NodeType3(a, b, childs) =>
      NodeType3(a, b, childs.map(update(_, id, val))
    ...  
    case Leaf(`id`, oldVal) =>
      Leaf(id, val)
  }
}

树中的所有节点都只是“驱动通过”,除了具有正确 id 的 Leaf 节点,它已更新。我有大约 27 种不同的节点类型,所以匹配块变得非常大。

这种匹配代码能不能用更简洁的方式表达?我不在乎代码不会修改树,我只是希望它变得更短。

【问题讨论】:

    标签: scala tree-traversal case-class


    【解决方案1】:

    这个习惯用法 - 将函数(在您的情况下,替换特定 id 的值)应用于结构内的值 - 可以用 Cats 中的 Functor 表示:

    trait Functor[F[_]] {
      def map[A, B](fa: F[A])(f: A => B): F[B]
    }
    

    您可以像这样为您的树实现一次map 函数:

    implicit val functorForTree: Functor[Tree] = new Functor[Tree] {
      def map[A, B](t: Tree[A])(f: A => B): Tree[B] = t match {
        case NodeType1(childs)    => NodeType1(childs.map(_.map(f))
        case NodeType2(a, childs) => NodeType2(a, childs.map(_.map(f))
        ...
        case LeafNode(a)          => LeafNode(f(a))
      }
    

    请注意,要实现这一点,Tree 必须由叶类型参数化,在您的情况下为 Leaf(id, val),即,而不是

    sealed trait Tree
    case class NodeType1(childs: List[Tree]) extends Tree
    case class Leaf(id, val) extends Tree
    

    你需要

    sealed trait Tree[Leaf]
    case class NodeType1[Leaf](childs: List[Tree[Leaf]]) extends Tree[Leaf]
    case class NodeType2[Leaf](a: String, childs: List[Tree[Leaf]]) extends Tree[Leaf]
    ...
    case class LeafNode[Leaf](leaf: Leaf) extends Tree[Leaf]
    
    case class OriginalLeaf(id: String, val: Int)
    type OriginalTree = Tree[OriginalLeaf]
    

    现在你的例子变成了:

    def update(tree: OriginalTree, id: String, val: Int): OriginalTree = tree.map {
      case OriginalLeaf(oldId, _) if oldId == id => OriginalLeaf(id, val)
      case anyOtherLeaf                          => anyOtherLeaf
    }
    

    如果不同节点类型的case写一次都麻烦,可以使用Kittens推导出Functor实例automatically

    implicit val functorForTree: Functor[Tree] = {
      import cats.derived._
      import auto.functor._
      semi.functor
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-25
      • 2021-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-22
      相关资源
      最近更新 更多