Reputation: 4053
Let's have this code:
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
This works:
scala> (Num(0) |) inc
res7: Num = Num(1)
But is it possible to somehow (maybe implicits or macros?) make Scala to run sample bellow in a same way as the code with parentheses without modifying Num
class?
scala> Num(0) | inc
<console>:11: error: Num does not take parameters
Num(0) | inc
^
Wanted result is:
scala> Num(0) | inc | inc
res8: Num = Num(2)
EDIT:
Here's code that's much closer to the real thing. I hope this is more understandable.
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() >>)
}
I realize that wanted operator adds no extra value in terms of functionality, it's supposed to be just a syntactic sugar to make things look nicer (in this case I'm trying to mimic the shell piping).
Upvotes: 0
Views: 596
Reputation: 4053
The answer from Akos Krivachy is pretty close, but since I can't append my complete solution to it I have to create a new separate answer (this feature of SO seems a bit weird to me).
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 >>)
}
Upvotes: 0
Reputation: 4966
Postfix vs Infix
Let's first look at how infix and postfix notation relate to each other. Given your case when you write Num(0) | inc
then that is equivalent to Num(0).|(inc)
in postfix notation.
So looking at your desired syntax:
Num(0) | inc | inc
This will be equivalent to the following in postfix notation:
Num(0).|(inc).|(inc)
Ok, now that that's clear let's do this!
The solution
The def |
needs to take a parameter, that holds the function it should do. There are two solutions here, either we define a function over Num
or we define a function over the Int
the Num
is actually holding:
implicit class Pipe(n:Num){
def |(fn: (Num) => Num) = fn(n)
def |(fn: (Int) => Int) = Num(fn(n.n))
}
Both would work - you need to choose which one will work best for you.
Now that we have this, we need to define this function. You could put this in Num
's companion object (also providing the two differing implementations):
object Num {
val incNum: Num => Num = n => Num(n.n + 1)
val inc = (i: Int) => i + 1
}
Looks like we're done. Now we just need to import these functions from the object and use them. The whole code:
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)
Upvotes: 3