代码之家  ›  专栏  ›  技术社区  ›  ideaboxer

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

  •  -1
  • ideaboxer  · 技术社区  · 6 年前

    因为提取器不能接受自定义参数(如中所述 Stack Overflow: Can extractors be customized... ),我试图找到解决以下问题的替代方法。

    我有很多可以合并的翻译。在我的代码片段中,维度可以与因子组合。比如说 "width multiplied by 2" .但也可能是 "width" (未乘以)。还有更多类似的案例。我尝试使用模式匹配对这些字符串输入进行分类。 “宽度” "width multiplied by x" 应分类为“宽度”(键 "w" ),则, "height" "height multiplied by x" 应归类为“高度”(键 "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
    
    3 回复  |  直到 6 年前
        1
  •  3
  •   Alexey Romanov    6 年前

    您可以轻松创建参数化 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
  •  2
  •   som-snytt    6 年前

    您的问题可能重复 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"
        }
      }
    }
    

    自定义将内置到case表达式中的选择中。该字符串用于构造所需的提取器。

    $ scalac ex.scala && scala ex.Test 24sec
    24 sec
    
    $ scalac ex.scala && scala ex.Test 60min
    60 minutes
    
        3
  •  1
  •   Andrey Tyukin    6 年前

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

    这里有一个建议:

    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 是一个适当的case类,它将维度和修改器作为成员。这个 CommandModifier s被建模为一个单独的封闭特征。这个 Dimension s(宽度、高度等)本质上只是一个枚举。短魔术值字符串 "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")
      }
    }
    

    输出:

    ------------------------------------------------------------
    命令(W,乘以(2))
    宽度乘以2
    布雷特mal 2
    ------------------------------------------------------------
    命令(T,未修改)
    顶部
    奥本
    ------------------------------------------------------------
    命令(H,乘以(42))
    高度乘以42
    Hhe mal 42
    

    请注意,字符串拆分和模式匹配的整个方法是 极其 易碎,根本不起作用。如果您想正确地完成它,您必须编写一个正确的解析器(使用解析器生成器或解析器组合器库)。

    推荐文章