10

Scala 中是否有允许提取器采用自定义参数的语法?这个例子有点做作。假设我有一个整数的二叉搜索树,如果它的值可以被某个自定义值整除,我想在当前节点上匹配。

使用 F# 活动模式,我可以执行以下操作:

type Tree =
    | Node of int * Tree * Tree
    | Empty  

let (|NodeDivisibleBy|_|) x t =
    match t with
    | Empty -> None
    | Node(y, l, r) -> if y % x = 0 then Some((l, r)) else None

let doit = function
    | NodeDivisibleBy(2)(l, r) -> printfn "Matched two: %A %A" l r
    | NodeDivisibleBy(3)(l, r) -> printfn "Matched three: %A %A" l r
    | _ -> printfn "Nada"

[<EntryPoint>]
let main args =
    let t10 = Node(10, Node(1, Empty, Empty), Empty)
    let t15 = Node(15, Node(1, Empty, Empty), Empty)

    doit t10
    doit t15

    0

在 Scala 中,我可以做类似的事情,但不是我想要的:

sealed trait Tree
case object Empty extends Tree
case class Node(v: Int, l: Tree, r: Tree) extends Tree

object NodeDivisibleBy {
  def apply(x: Int) = new {
    def unapply(t: Tree) = t match { 
      case Empty => None
      case Node(y, l, r) => if (y % x == 0) Some((l, r)) else None
    }
  }
}

def doit(t: Tree) {
  // I would prefer to not need these two lines.
  val NodeDivisibleBy2 = NodeDivisibleBy(2)
  val NodeDivisibleBy3 = NodeDivisibleBy(3)
  t match { 
    case NodeDivisibleBy2(l, r) => println("Matched two: " + l + " " + r)
    case NodeDivisibleBy3(l, r) => println("Matched three: " + l + " " + r)
    case _ => println("Nada")
  }
}

val t10 = Node(10, Node(1, Empty, Empty), Empty)
val t15 = Node(15, Node(1, Empty, Empty), Empty)

doit(t10)
doit(t15)

如果我能做到,那就太好了:

case NodeDivisibleBy(2)(l, r) => println("Matched two: " + l + " " + r)
case NodeDivisibleBy(3)(l, r) => println("Matched three: " + l + " " + r)

但这是一个编译时错误: '=>' 预期但 '(' 找到。

想法?

4

5 回答 5

6

规格

   SimplePattern ::= StableId ‘(’ [Patterns] ‘)’

提取器模式x(p1, ..., pn)其中n ≥ 0与构造器模式具有相同的句法形式。但是,稳定标识符x不是案例类,而是表示一个对象,该对象具有一个名为unapplyunapplySeq匹配模式的成员方法。

和:

稳定标识符是以标识符结尾的路径。

即,不是像NodeDivisibleBy(2).

所以不,这在 Scala 中不可能以任何直接的方式实现,我个人认为这很好:必须编写以下内容(顺便说一下,我可能会在NodeDivisibleBy对象和导入中定义):

val NodeDivisibleBy2 = NodeDivisibleBy(2)
val NodeDivisibleBy3 = NodeDivisibleBy(3)

对于不必破译 case 子句中的任意表达式的可读性增加,这是一个很小的代价。

于 2012-11-06T01:43:16.173 回答
4

正如 Travis Brown 所指出的,在 scala 中这是不可能的。

在那种情况下,我所做的只是使用警卫和别名将分解与测试分开。

val DivisibleBy = (n: Node, x: Int) => (n.v % x == 0) 

def doit(t: Tree) = t match { 
    case n @ Node(y, l, r) if DivisibleBy(n,2) => println("Matched two: " + l + " " + r)
    case n @ Node(y, l, r) if DivisibleBy(n,3) => println("Matched three: " + l + " " + r)
    case _ => println("Nada")
}

在这种简单的情况下,定义一个单独DivisibleBy的显然是完全过分的,但可能会以类似于 F# 活动模式的方式在更复杂的场景中提高可读性。

您还可以将 divisibleBy 定义为 Node 的方法并具有:

case class Node(v: Int, l: Tree, r: Tree) extends Tree {
    def divisibleBy(o:Int) = (v % o)==0
}

def doit(t: Tree) = t match { 
    case n @ Node(y, l, r) if n divisibleBy 2 => println("Matched two: " + l + " " + r)
    case n @ Node(y, l, r) if n divisibleBy 3 => println("Matched three: " + l + " " + r)
    case _ => println("Nada")
}

我认为它比 F# 版本更具可读性(如果更详细的话)

于 2012-11-06T06:28:41.097 回答
2

将案例类、谓词和参数绑定在一起,然后照常匹配结果。

case class Foo(i: Int)

class Testable(val f: Foo, val ds: List[Int])

object Testable {
  def apply(f: Foo, ds: List[Int]) = new Testable(f, ds)
  def unapply(t: Testable): Option[(Foo, List[Int])] = {
    val xs = t.ds filter (t.f.i % _ == 0)
    if (xs.nonEmpty) Some((t.f, xs)) else None
  }
}

object Test extends App {
  val f = Foo(100)

  Testable(f, List(3,5,20)) match {
    case Testable(f, 3 :: Nil)  => println(s"$f matched three")
    case Testable(Foo(i), 5 :: Nil) if i < 50
                                => println(s"$f matched five")
    case Testable(f, ds)        => println(s"$f matched ${ds mkString ","}")
    case _                      => println("Nothing")
  }
}
于 2012-11-06T08:08:14.917 回答
0

迟到的答案,但有一个 scalac 插件提供语法~(extractorWithParam(p), bindings),编译时所有安全:https ://github.com/cchantep/acolyte/tree/master/scalac-plugin#match-component

于 2014-02-24T15:46:20.670 回答
-1

据我所知,答案是否定的。

对于这种情况,我也使用以前的方式。

于 2012-11-06T01:29:13.370 回答