17

I am new to Scala. I wonder whether it is possible to define some precedence with method calls. For example, if I have the chain of method calls:

someObject method1 param1 method2 param2 method3 param3

can this be equivalent to the following:

someObject.method1(param1).method2(param2.method3(param3))

or

someObject method1 param1 method2 (param2 method3 param3)

So I want method3 to take precedence over method2...

The reason I want to do this is that I want to develop a DSL, so I want to avoid using dots and parentheses as much as possible. If you guys find another solution for me, feel free to let me know.

Jonas
  • 121,568
  • 97
  • 310
  • 388
Peter
  • 509
  • 1
  • 3
  • 10
  • 2
    You may want to read http://www.manning.com/ghosh/ (DSLs in Action) as it will help explain a great deal. He covers Ruby, Scala (mostly Scala), Clojure and Groovy. – James Black Sep 29 '11 at 12:53

2 Answers2

12

You'll have to use methods with special operator characters to influence precedence as implied by Tomasz. This is partly why lots of Scala DSL make heavy use of operators. Also why some DSL are hard to read if you don't work with them daily.

Given method with using only letters, underscore and digits - you won't be able to influence things, here is what I put together for myself after reading the spec:

  • Any method which takes a single parameter can be used as an infix operator: a.m(b) can be written a m b.
  • Any method which does not require a parameter can be used as a postfix operator: a.m can be written a m.

  • Postfix operators have lower precedence than infix operators, so foo bar baz means foo.bar(baz) while foo bar baz bam means (foo.bar(baz)).bam and foo bar baz bam bim means (foo.bar(baz)).bam(bim).

So without knowing at all what your method signatures are, the following code (because it's all alphanumeric):

someObject method1 param1 method2 param2 method3 param3

will be parsed as:

someObject.method1(param1).method2(param2).method3(param3)

If you rename method3 to |*| or +:+ or whatever operator makes sense, you can achieve what you want:

someObject method1 param1  method2 param2 |*| param3
// same as
someObject.method1(param1).method2(param2.|*|(param3))

For example to see the difference:

implicit def pimp(s:String) = new {
    def |*|(t:String) = t + s
    def switch(t:String) = t + s 
}

scala> "someObject" concat "param1" concat "param2" |*| "param3"
res2: java.lang.String = someObjectparam1param3param2

scala> "someObject" concat "param1" concat "param2" switch "param3"
res3: java.lang.String = param3someObjectparam1param2
huynhjl
  • 41,520
  • 14
  • 105
  • 158
  • Your answer is very helpful :). But I really want my DSL to be easy to understand and intuitive to use, so I cannot use methods with non-alphanumeric characters. Is there a way to mix letters and non-alphanumeric characters for a method name??? – Peter Sep 29 '11 at 15:57
  • @Peter, Not in a way that's useful. You can name a method `foo_?` or `foo_#%^` but it's the first letter of the method that determine precedence and if the method name starts with an operator character it has to be made entirely of operator characters. Note that the DSL can still be intuitive if you use special characters but you need to be careful. For instance I find most of the operators in http://www.scala-lang.org/api/current/scala/sys/process/ProcessBuilder.html easy to remember, while I have a hard time with http://dispatch.databinder.net/Two+Handlers+Are+Better+Than+One.html. – huynhjl Sep 30 '11 at 04:17
4

This behavior is defined in chapter 6.12.3 Infix Operations of The Scala Language Specification.

In short: methods are invoked from left to right by default with some exceptions. These exceptions were only introduced to support mathematical operators precedence. So when you have two functions named * and +:

a + b * c

This will always be translated to:

a.+(b.*(c))

Here the first name of the function controls the precedence. However for ordinary functions you cannot control the order. Think about it - this would actually cause havoc and terribly unmaintainable code.

See also (not quite duplicate?): Operator precedence in Scala.

Community
  • 1
  • 1
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • 1
    Note that the spec is wrong - see http://article.gmane.org/gmane.comp.lang.scala/24402 and http://stackoverflow.com/questions/7022207/why-scala-changed-relative-precedence-of-relational-vs-equality-operators-compar/7022704#7022704. – huynhjl Sep 29 '11 at 14:24