Scala:不可参数化提取器的解决方法

Scala: Workaround for unparameterizable extractor

由于提取器无法采用自定义参数(如 Stack Overflow: Can extractors be customized... 中的回答),我试图找到解决以下问题的替代方法。

我有很多翻译可以合并。在我的代码片段中,维度可以与因子组合。例如"width multiplied by 2"。但它也可以是 "width"(不相乘)。还会有更多这样的案例。我尝试使用模式匹配对这些字符串输入进行分类。 "width""width multiplied by x"应该归类为"width"(key"w"),"height""height multiplied by x"应该归类为"height"(键 "h"), 依此类推。

这应该由以下示例代码片段中的最后一个 match 完成,其中将包含许多案例(示例代码片段中有 6 个),每个案例都应采用一个 key: String 参数( "w""h""l""r""t""b").

我试图实现的是传递密钥(即"w""h""l""r""t""b" 等等)到案例 Untranslation(v)。但显然我不能那样做(unapply 函数可以采用隐式参数,但没有额外的显式参数)。

现在我尝试找到一种替代但仍然简洁的方法来对我的字符串输入进行分类。

implicit val translations = Map(
  "w" -> "width",
  "h" -> "height",
  "l" -> "left",
  "r" -> "right",
  "t" -> "top",
  "b" -> "bottom",
  // + some more translations
  "m" -> "multiplied by"
)

sealed trait CommandType
object CommandType {
  case object Unmodified extends CommandType
  case object Multiplied extends CommandType
  // ...
}

object Untranslation {
  def unapply(s: String)(implicit t: Map[String, String]): Option[CommandType] = {
    val key: String = "w" // should be variable by case
    val a: List[String] = t(key).split(" ").toList
    val b: List[String] = t("m").split(" ").toList
    val ab: List[String] = a ++ b
    s.split(" ").toList match {
      case `a` => Some(CommandType.Unmodified)
      case `ab` :+ value => Some(CommandType.Multiplied)
      // + some more cases
      case _ => None
    }
  }
}

"width multiplied by 2" match {
  case Untranslation(v) => println(v) // here I would like to pass the key ("w"/"h"/"l"/...)
  case _ => println("nothing found")
}
// outputs: Multiplied

您可以轻松地为提取器创建参数化 class 而不是 object:

class Untranslation(val key: String) {
  def unapply(s: String)(implicit t: Map[String, String]): Option[CommandType] = {
    val a: List[String] = t(key).split(" ").toList
    val b: List[String] = t("m").split(" ").toList
    val ab: List[String] = a ++ b
    s.split(" ").toList match {
      case `a` => Some(CommandType.Unmodified)
      case `ab` :+ value => Some(CommandType.Multiplied)
      // + some more cases
      case _ => None
    }
  }
}

match,一个提取器需要有一个稳定的标识符,这可以通过将它分配给一个val来完成(所以不幸的是每个键都需要一个额外的行,但是当然它们可以在多场比赛中使用):

val UntranslationW = new Untranslation("w")
val UntranslationT = new Untranslation("t")
...

"width multiplied by 2" match {
  case UntranslationW(v) => ...
  case UntranslationT(v) => ...
  case _ => println("nothing found")
}

无论你是否想实现一个合适的解析器,你至少应该创建能够忠实地表示你的命令的数据结构。

这是一个提议:

sealed trait Dimension {
  def translate(implicit t: Map[Symbol, String]) = 
    t(Symbol(toString.toLowerCase))
}
case object W extends Dimension
case object H extends Dimension
case object L extends Dimension
case object R extends Dimension
case object T extends Dimension
case object B extends Dimension
object Dimension {
  def all = List(W, H, L, R, T, B)
}

sealed trait CommandModifier {
  def translate(implicit t: Map[Symbol, String]): String
}
case object Unmodified extends CommandModifier {
  def translate(implicit t: Map[Symbol, String]) = ""
}
case class Multiplied(factor: Int) extends CommandModifier {
  def translate(implicit t: Map[Symbol, String]) = t('m) + " " + factor
}


case class Command(dim: Dimension, mod: CommandModifier) {
  def translate(implicit t: Map[Symbol, String]) = 
    dim.translate + " " + mod.translate
}

A Command 是一个适当的案例 class,它具有维度和修饰符作为成员。 CommandModifier 被建模为单独的密封特征。 Dimensions(宽度、高度等)本质上只是一个枚举。短魔法值字符串 "w""h" 已被符号 'w'h 等替换

现在您可以实现一个 Untranslation 提取器,一次性提取整个命令,因此不需要任何额外的参数:

object Untranslation {
  def unapply(s: String)(implicit t: Map[Symbol, String]): Option[Command] = {
    val sParts = s.split(" ").toList
    for (dim <- Dimension.all) {
      val a: List[String] = dim.translate.split(" ").toList
      val b: List[String] = t('m).split(" ").toList
      val ab: List[String] = a ++ b
      sParts match {
        case `a` => return Some(Command(dim, Unmodified))
        case `ab` :+ value => return Some(Command(dim, Multiplied(value.toInt)))
        // + some more cases
        case _ => None
      }
    }
    None
  }
}

一个小例子。以下是如何用英语和德语解析和写出命令。首先,将形式符号映射到自然语言中实际单词的两个词典:

val En = Map(
  'w -> "width",
  'h -> "height",
  'l -> "left",
  'r -> "right",
  't -> "top",
  'b -> "bottom",
  'm -> "multiplied by"
)

val De = Map(
  'w -> "Breite",
  'h -> "Höhe",
  'l -> "links",
  'r -> "rechts",
  't -> "oben",
  'b -> "unten",
  'm -> "mal"
)

使用 En 词典,您现在可以匹配英文命令:

for (example <- List(
  "width multiplied by 2",
  "top",
  "height multiplied by 42"
)) {
  println("-" * 60)
  implicit val lang = En
  example match {
    case Untranslation(v) => {
      println(v)
      println(v.translate(En))
      println(v.translate(De))
    }
    case _ => println("invalid command")
  }
}

这是匹配的内容,以及它是如何用英语和德语翻译的:

------------------------------------------------------------
Command(W,Multiplied(2))
width multiplied by 2
Breite mal 2
------------------------------------------------------------
Command(T,Unmodified)
top 
oben 
------------------------------------------------------------
Command(H,Multiplied(42))
height multiplied by 42
Höhe mal 42

反之亦然,从德语到英语:

for (example <- List(
  "Breite mal 2",
  "oben",
  "Höhe mal 42"
)) {
  println("-" * 60)
  implicit val lang = De
  example match {
    case Untranslation(v) => {
      println(v)
      println(v.translate(En))
      println(v.translate(De))
    }
    case _ => println("invalid command")
  }
}

输出:

------------------------------------------------------------
Command(W,Multiplied(2))
width multiplied by 2
Breite mal 2
------------------------------------------------------------
Command(T,Unmodified)
top 
oben 
------------------------------------------------------------
Command(H,Multiplied(42))
height multiplied by 42
Höhe mal 42

请注意,包含字符串拆分和模式匹配的整个方法非常 脆弱,根本无法扩展。如果你想正确地做到这一点,你必须编写一个合适的解析器(使用解析器生成器,或者使用解析器组合器库)。

您的问题可能重复 this one

package ex

import language._

object units extends Dynamic {
  class Helper(kind: String) {
    val kindof = kind match {
      case "s" => Symbols.s
      case "m" => Symbols.m
    }
    def value = raw"(\d+)${kindof.name}".r
    object pair {
      def unapply(s: String): Option[(Int, Symbol)] =
        value.unapplySeq(s).map(vs => (vs.head.toInt, kindof))
    }
  }
  def selectDynamic(kind: String) = new Helper(kind)
  object Symbols { val s = 'sec ; val m = 'min }
}

object Test {
  def main(args: Array[String]): Unit = println {
    args(0) match {
      case units.s.pair(x, s) => s"$x ${s.name}"
      case units.s.value(x) => s"$x seconds"
      case units.m.value(x) => s"$x minutes"
    }
  }
}

自定义已内置到案例表达式中的选择中。该字符串用于构建所需的提取器。

$ scalac ex.scala && scala ex.Test 24sec
24 sec

$ scalac ex.scala && scala ex.Test 60min
60 minutes