2013-11-24 1 views
0

Давайте этот код:Как создать настраиваемый оператор, имитирующий точку (без круглых скобок)?

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) 

Но можно как-то (возможно, implicits или макросы?) Делают Scala для запуска образца, приведенную ниже в той же образом, как код с круглыми скобками без изменения Num класс?

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

Wanted результат:

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

EDIT:
Вот код, который гораздо ближе к реальной вещи. Надеюсь, это более понятно.

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

answer из Акос Krivachy довольно близко, но так как я не могу добавить мое полное решение для этого я должен создать новый отдельный ответ (эта особенность SO кажется немного странным меня).

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 >>) 
} 
3

Postfix против Infix

первый взгляд Давайте, как инфикс и постфикса обозначения относятся друг к другу. Учитывая ваш случай, когда вы пишете Num(0) | inc, это эквивалентно Num(0).|(inc) в постфиксной нотации.

Так, глядя на ваш желаемый синтаксис:

Num(0) | inc | inc 

Это будет эквивалентно следующему в постфикса нотации:

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

Хорошо, теперь это ясно давайте сделаем это!

Раствор

В def | необходимо принять параметр, который содержит функцию она должна делать. Есть два решения здесь, либо мы определим функцию над Num или мы определим функцию над IntNum фактически держит:

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

И будет работать - вы должны выбрать, какой из них будет работать лучше для вас.

Теперь, когда мы имеем это, мы должны определить эту функцию. Вы можете поместить это в объект компаньон Num «s (также предоставляет две различные реализации):

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

Только намек, если вы используете свободные функции много, то вообще 'труба вперед' оператор a'la F # может стать полезным: 'неявного класса ForwardPipe [T, U] (Arg: T) {def |> (f: T => U) = f (arg)} ' –

+0

Да - это хорошо! –

+0

Спасибо за сообщение. Но я не уверен, что смогу это использовать. Первая проблема заключается в том, что я понятия не имею, как я могу расширить это решение для принятия параметров (он должен имитировать «точку», но, возможно, частично применяемые функции могли бы сделать трюк?). Также я надеялся на более общее решение - класс, который я хотел сутенер, имеет десятки (возможно, hundread) методов, это означало бы, что мне придется создавать обертки для каждого метода ... – monnef

Смежные вопросы