【问题标题】:Scala: Workaround for unparameterizable extractorScala:不可参数化提取器的解决方法
【发布时间】:2018-10-22 07:39:50
【问题描述】:

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

我有很多可以组合的翻译。在我的代码 sn-p 中,一个维度可以与一个因子相结合。例如"width multiplied by 2"。但它也可以是"width"(未相乘)。而且还会有更多类似的案例。我尝试使用模式匹配对这些字符串输入进行分类。 "width""width multiplied by x" 应归类为“宽度”(键"w"),"height""height multiplied by x" 应归类为“高度”(键"h"),依此类推。

这应该由以下示例代码 sn-p 中的最后一个 match 完成,其中将包含许多案例(示例代码 sn-p 中的 6 个)每个案例都应采用 key: String 参数(@987654332 @、"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

【问题讨论】:

  • 我完全不明白你想在那里做什么。如果你想用“命令”做一些事情,你为什么不正确地解析它,然后相应地解释解析的结构呢?为什么要“分类”任何东西?为什么不能只检查命令startsWith 是否有某个词?我什至不明白您所说的“翻译”是什么意思:您是指几何翻译(似乎是半可信的,因为您在谈论宽度和高度以及因素等),还是您的意思是语言之间的翻译(哪些语言?) ?
  • @AndreyTyukin,我必须猜测不是 OP,但我认为这个案例与不同的人类语言有关。我想目标是将一个以已知固定语言传入的字符串解析为内部抽象表示。并且计划似乎是t 自己包含所有相关部分的翻译。所以对于英语t("w")"width"t("m")"multiplied by"。看起来 OP 希望使用模式匹配来解析将映射到 "wm(value)""hm(value)" 等等的字符串,而只是 "w" 或只是 "h" 等等。
  • @SergGr 如果是这种情况,那么 OP 应该阅读有关具有注意力机制的堆叠长短期记忆神经网络以及什么不是...尝试用一些复杂的硬-编码模式匹配似乎与其他人所做的一切相去甚远。
  • @AndreyTyukin,我同意现实世界的人类语言比这种简单的结构要复杂得多,但可能是被解析的文本实际上是一种完全符合这种结构的伪自然语言。更重要的是,您问了这个问题的含义,在我看来,我上面的解释非常适合代码和文本。

标签: scala pattern-matching extractor unapply


【解决方案1】:

您可以轻松地为提取器创建参数化的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")
}

【讨论】:

    【解决方案2】:

    您的问题可能与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
    

    【讨论】:

    • 谢谢。这几乎是我所需要的,但我与您示例中的单位的等价物将存储为符号。如何用符号替换units.sunits.m 等? (输入Symbol
    • Symbol(kind).
    • 我需要写units.MyObject.theSymbol,其中theSymbol 是定义为对象成员的符号。这给了我一个编译器错误。
    • 我不能再拆箱你的想法。如果TheSymbol(表示您的单位?)是一个稳定的参考,您可以使用(value, unit)case Extract(value, TheSymbol) 的常规提取器,大写TheSymbol(或反引号)使其匹配该值。可能我不知道你想要什么语法。
    • 应该可以使用 shapeless 在模式 s 和记录成员 s 的值 'sec 之间提供编译时安全性,但是我遇到了编译器崩溃;如果我以后能弄清楚,我会更新。
    【解决方案3】:

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

    这是一个建议:

    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
    }
    

    Command 是一个适当的案例类,它具有维度和修饰符作为成员。 CommandModifiers 被建模为一个单独的密封特征。 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-dictionary,您现在可以匹配英文命令:

    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
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-20
      • 2013-11-29
      • 2011-09-09
      • 2014-07-11
      • 2013-03-08
      • 1970-01-01
      • 2012-03-21
      • 1970-01-01
      相关资源
      最近更新 更多