如何创建模拟点的自定义运算符(没有括号)?

时间:2013-11-24 20:47:18

标签: scala

我们有这个代码:

scala> case class Num(n:Int){def inc = Num(n+1)}
defined class Num

scala> implicit class Pipe(n:Num){ def | = n }
defined class Pipe

这有效:

scala> (Num(0) |) inc
res7: Num = Num(1)

但有可能以某种方式(可能是暗示或宏吗?)使Scala以与带括号的代码相同的方式运行示例而不修改Num类?

scala> Num(0) | inc
<console>:11: error: Num does not take parameters
              Num(0) | inc
                     ^

通缉的结果是:

scala> Num(0) | inc | inc
res8: Num = Num(2)

编辑:
这里的代码更接近真实的东西。我希望这更容易理解。

object ApplyTroubles2 extends App {

  import GrepOption.GrepOption

  abstract class Builder {
    var parent: Builder = null

    def getOutput: String

    def append(ch: Builder) = { ch.parent = this; ch }

    def echo(s: String) = append(new Echo(s))

    def wc() = append(new Wc())

    def grep(s: String, opts: Set[GrepOption]) = append(new Grep(s, opts))

    def grep(s: String) = append(new Grep(s))
  }

  object MainBuilder extends Builder {
    def getOutput: String = ""

    override def append(ch: Builder): Builder = ch
  }

  class Echo(data: String) extends Builder {
    def getOutput = data
  }

  class Wc() extends Builder {
    def getOutput = parent.getOutput.size.toString
  }

  class Grep(var pattern: String, options: Set[GrepOption]) extends Builder {
    def this(pattern: String) = this(pattern, Set.empty)

    val isCaseInsensitive = options.contains(GrepOption.CASE_INSENSITIVE)

    if (isCaseInsensitive) pattern = pattern.toLowerCase

    def getOutput = {
      val input = if (isCaseInsensitive) parent.getOutput.toLowerCase else parent.getOutput
      if (input.contains(pattern)) input
      else ""
    }
  }

  object GrepOption extends Enumeration {
    type GrepOption = Value
    val CASE_INSENSITIVE = Value
  }

  object BuilderPimps {
    //    val wc: Builder => Builder = x => x.wc()
    //    def echo(msg: String): Builder => Builder = x => x.echo(msg)
  }

  implicit class BuilderPimps(b: Builder) {
    // as suggested in one answer, should solve calling an apply method
    //    def |(fn: Builder => Builder): Builder = fn(b)

    def | : Builder = b

    def getStringOutput: String = b.getOutput

    def >> : String = getStringOutput
  }

  import MainBuilder._

  // working
  println(echo("xxx").wc().getOutput)
  println(echo("str") getStringOutput)
  println((echo("y") |) wc() getStringOutput)
  println(((((echo("y") |) echo ("zz")) |) wc()) >>)
  println(((echo("abc") |) grep ("b")) >>)
  println((echo("aBc") |) grep("AbC", Set(GrepOption.CASE_INSENSITIVE)) getStringOutput)

  // not working
  println((echo("yyyy") | wc()) getStringOutput)
  println(echo("yyyy") | wc() getStringOutput)
  println((echo("y")|) grep("y") >>)
  println(echo("x") | grep("x") | wc() >>)
}

我意识到想要的操作符在功能方面没有额外的价值,它应该只是一个语法糖,使事情看起来更好(在这种情况下,我试图模仿外壳管道)。

2 个答案:

答案 0 :(得分:3)

Postfix vs Infix

让我们先来看看中缀和后缀符号是如何相互关联的。根据您编写Num(0) | inc时的情况,这相当于后缀表示法中的Num(0).|(inc)

所以看看你想要的语法:

Num(0) | inc | inc

这与后缀表示法中的以下内容相同:

Num(0).|(inc).|(inc)

好的,现在很清楚,让我们这样做!

解决方案

def |需要一个参数,它保存应该执行的功能。这里有两个解决方案,要么我们定义一个函数而不是Num,要么我们定义了Int Num实际持有的函数:

implicit class Pipe(n:Num){
  def |(fn: (Num) => Num) = fn(n)
  def |(fn: (Int) => Int) = Num(fn(n.n))
}

两者都有效 - 您需要选择哪一种最适合您。

现在我们有了这个,我们需要定义这个功能。你可以将它放在Num的伴随对象中(也提供两个不同的实现):

object Num {
  val incNum: Num => Num = n => Num(n.n + 1)
  val inc = (i: Int) => i + 1
}

看起来我们已经完成了。现在我们只需要从对象中导入这些函数并使用它们。整个代码:

case class Num(n:Int)
object Num {
  val incNum: Num => Num = n => Num(n.n + 1)
  val inc = (i: Int) => i + 1
}

implicit class Pipe(n:Num){
  def |(fn: (Num) => Num) = fn(n)
  def |(fn: (Int) => Int) = Num(fn(n.n))
}

import Num._
Num(0) | inc | inc         // Num(2)
Num(0) | incNum | incNum   // Num(2)

答案 1 :(得分:0)

来自Akos Krivachy的answer非常接近,但由于我无法将完整的解决方案添加到其中,我必须创建一个新的单独答案(SO的这个特征对我来说似乎有点奇怪)。 / p>

object ApplyTroubles2 extends App {

  import GrepOption.GrepOption

  abstract class Builder {
    var parent: Builder = null

    def getOutput: String

    def append(ch: Builder) = { ch.parent = this; ch }

    def echo(s: String) = append(new Echo(s))

    def wc() = append(new Wc())

    def grep(s: String, opts: Set[GrepOption]) = append(new Grep(s, opts))

    def grep(s: String) = append(new Grep(s))
  }

  object MainBuilder extends Builder {
    def getOutput: String = ""

    override def append(ch: Builder): Builder = ch
  }

  class Echo(data: String) extends Builder {
    def getOutput = data
  }

  class Wc() extends Builder {
    def getOutput = parent.getOutput.size.toString
  }

  class Grep(var pattern: String, options: Set[GrepOption]) extends Builder {
    def this(pattern: String) = this(pattern, Set.empty)

    val isCaseInsensitive = options.contains(GrepOption.CASE_INSENSITIVE)

    if (isCaseInsensitive) pattern = pattern.toLowerCase

    def getOutput = {
      val input = if (isCaseInsensitive) parent.getOutput.toLowerCase else parent.getOutput
      if (input.contains(pattern)) input
      else ""
    }
  }

  object GrepOption extends Enumeration {
    type GrepOption = Value
    val CASE_INSENSITIVE = Value
  }

  // all above is un-touchable (e.g. code of a library I want to pimp out)

  // all bellow are the pimps I wanted
  // (
  //   based on this answer [https://stackoverflow.com/a/20181011/1017211]
  //   from Akos Krivachy [https://stackoverflow.com/users/1697985/akos-krivachy]
  // )

  object MyBuilder {
    type MyBuilderTransformer = MyBuilder => MyBuilder

    def builderFunc(func: Builder => Builder): MyBuilderTransformer =
      (x: MyBuilder) => {func(x.builder).wrap}

    // methods in original library without parameters can be represented as vals
    val wc: MyBuilderTransformer = builderFunc(_.wc())

    // when it has parameters it must be def, we need to pack params
    def grep(s: String): MyBuilderTransformer = builderFunc(_.grep(s))

    def grep(s: String, ss: Set[GrepOption]): MyBuilderTransformer = builderFunc(_.grep(s, ss))

    // root expression, differs a bit from original, but in this case it's good enough
    def fromString(msg: String): MyBuilder = MyBuilder(MainBuilder.echo(msg))
  }

  // wrapper class
  case class MyBuilder(builder: Builder)

  implicit class BuilderPimps(b: Builder) {
    def wrap = MyBuilder(b)
  }

  implicit class MyBuilderPimps(b: MyBuilder) {
    def |(fn: MyBuilder => MyBuilder): MyBuilder = fn(b)

    def getStringOutput: String = b.builder.getOutput

    def >> : String = getStringOutput
  }

  // this all works (shows how an end user would use this pimps)

  import MyBuilder._

  println(fromString("abc") | wc getStringOutput)
  println(fromString("abc") | wc >>)

  println(fromString("abc") | grep("b") | wc getStringOutput)
  println(fromString("abc") | grep("b") | wc >>)

  println(fromString("abc") | grep("B", Set(GrepOption.CASE_INSENSITIVE)) | wc getStringOutput)
  println(fromString("abc") | grep("B", Set(GrepOption.CASE_INSENSITIVE)) | wc >>)

  println(fromString("abc") | grep("y", Set(GrepOption.CASE_INSENSITIVE)) | wc getStringOutput)
  println(fromString("abc") | grep("y", Set(GrepOption.CASE_INSENSITIVE)) | wc >>)
}