Paul Draper
Paul Draper

Reputation: 83353

error: left- and right-associative operators with same precedence may not be mixed

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

Answers (2)

Zeimyth
Zeimyth

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

Odomontois
Odomontois

Reputation: 16328

There are two troubles in operators name you have chosen:

  • Name :// contains double slash, so without backquotes compiler can misinterpret it as comment
  • Name : 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

Related Questions