Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to create dot-free dsl in scala with two identifiers between variables?

Tags:

scala

Is there a way to define a dsl, that would allow the following form?

variable identifier identifier variable

For example:

1 equals to 2

I know how to create a simpler form: 1 equals to (2), but I want to avoid parentheses. Is there a way to do it?

like image 849
Rogach Avatar asked May 08 '12 18:05

Rogach


3 Answers

You can ask the parser:

$ scala -Xprint:parser
Welcome to Scala version 2.9.2 ... blah

scala> variable1 identifier1 identifier2 variable2
// lots of stuff and inside:
val res0 = variable1.identifier1(identifier2).variable2
// this is how the parser sees it.
// if you can make that work (dynamic classes…?), you’re good to go.

However, there is a problem: This only works as long as variable2 is an identifier (so that it can be used as a method name). With

scala> 1 equals to 2

already the parser fails:

<console>:1: error: ';' expected but integer literal found.
       1 equals to 2
                   ^

Parentheses are really your only way around(*):

scala> 1 equals to (2)
// ...
val res1 = 1.equals(to(2))

(*) unless you make 2 an identifier by using it with backticks

scala> 1 equals to `2`
// ...
val res2 = 1.equals(to).2

… nah, maybe not.

like image 69
Debilski Avatar answered Nov 07 '22 05:11

Debilski


As far as I know it's impossible at the moment to implement it as you want (I would be happy to be proven wrong). I also faced this issue, when I was creating my small DSL for injection. But I have realized, that even if you can't have 2 variables between 2 identifiers, you still can have three identifiers between them. It looks like this:

variable1 method constant method variable2

which is the same as:

variable1.method(constant).method(variable2)

With this I was able to come up with very nice DSL that looks like this:

by default defaultDb and identified by 'remote
'remote is by default defaultDb

You can find more examples of it's usage in this spec:

https://github.com/OlegIlyenko/scaldi/blob/48f7e4186cf7eb441116087003d7f45f16e0ac6c/src/test/scala/scaldi/InjectableSpec.scala#L47

It's implementation can be found here:

https://github.com/OlegIlyenko/scaldi/blob/master/src/main/scala/scaldi/Injectable.scala

I used ByWord and IdentifiedWord class types as method arguments. For example:

def is(by: ByWord) = and(by)

Which leaves possibility, that users of the library will extend ByWorld class and generally can provide their own ByWorld implementations. Now when I think about this, I find it not very nice. Better solution would be to create singelton objects for the constant words and then use their type like this:

object by
def is(byWord: by.type) = and(by)

This generally restricts argument to only one possible by word instance.

like image 32
tenshi Avatar answered Nov 07 '22 03:11

tenshi


Unary Operators

Well, you can have two successive identifiers if the final one is a unary operator:

a identifier1 - d

is parsed as

a.identifier1(d.unary_-)

Note that unary functions can be only ! ~ and -

Dynamic methods

If you don't mind to have your last variable name treated as a string (e.g. you have a way to later retrieve its value from a map or the interpreter itself), you can have the following using dynamics:

import scala.Dynamic
import scala.language.dynamics

class I2
class Res(i: I2) extends Dynamic {
  def selectDynamic(obj: String): Unit = {
      println(obj)
    }
}
class V1 { def i1(i2: I2): Res= new Res(i2) }
val v1 = new V1
val i2 = new I2

v1 i1 i2 v2

results in the string "v2".

like image 1
Mikaël Mayer Avatar answered Nov 07 '22 05:11

Mikaël Mayer