我们有这个代码:
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() >>)
}
我意识到想要的操作符在功能方面没有额外的价值,它应该只是一个语法糖,使事情看起来更好(在这种情况下,我试图模仿外壳管道)。
答案 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 >>)
}