Reputation: 83353
I'm trying to make a URI DSL in Scala, but infix methods are really giving me trouble.
Even after committing the lengthy and very unintuitive precedence rules to memory, they are still giving me trouble.
class Foo {
def `://`(a: Unit) = this
def `:`(b: Unit) = this
}
object Foo {
def main(args: Array[String]): Unit = {
new Foo `://` {} `:` {}
}
}
yields
left- and right-associative operators with same precedence may not be mixed
new Foo `://` {} `:` {}
^
What does this mean? I thought all operators were left-associative.
Is there any way for me to write a DSL that looks like this?
"https" `://` "example.com" `:` 80
Upvotes: 0
Views: 1177
Reputation: 1395
From The Scala Language Specification, section 6.12.3:
The associativity of an operator is determined by the operator’s last character. Operators ending in a colon ‘:’ are right-associative. All other operators are left-associative
The compiler doesn't know whether to interpret your code as this:
80.`:`("https".`://`("example.com"))
or this:
"https".`://`(80.`:`("example.com"))
I don't think there's a way to prevent ':' from being treated as a right-associative operator. You could help the compiler out by using parentheses; otherwise, you have to change your operator names.
Upvotes: 2
Reputation: 16328
There are two troubles in operators name you have chosen:
://
contains double slash, so without backquotes compiler can misinterpret it as comment:
as all other operators ending with :
creates right associative operator, this is handy for operators like ::
or #::
for building sequences starting from head. Operators with different associativity are not allowed without parenthesis since it's not clear where you should start building you expression. So my suggestion is get rid of double slash and colon-ending, create maybe a little bit confusing, but correct DSL syntax:
object URILanguage extends App {
case class URL(protocol: String, hostname: String, port: Option[Int] = None, path: Seq[String] = Nil) {
def %(port: Int) = copy(port = Some(port))
def /(component: String) = copy(path = path :+ component)
}
implicit class WithHostname(protocol: String) {
def ~(hostname: String) = URL(protocol, hostname)
}
println("http" ~ "example.com" % 8080 / "mysite" / "index.html")
}
Upvotes: 3