Search code examples
scalasyntaxmacros

Is <- only accessible by the compiler


Scala's <- arrow seems a bit strange. Most operators are implemented somewhere in the source as a function, defined on a data type, either directly or implicitly. <- on the other hand only seems to unusable outside of a for comprehension, where it acts as a syntactic element used to signal the binding of a new variable in a monadic context (via map).

This is the only instance I can think of where Scala has an operator-looking syntactical element that is only usable in a specific context, and isn't an actual function.

Am I wrong about how <- works? Is it a special case symbol used just by the compiler, or is there some way a developer could use this behavior when writing their own code?

For example, would it be possible to write a macro to transform

forRange (i <- 0 to 10) { print(i) } 

into

{ var i = 0; while (i <= 10) { print(i) } }

instead of its standard map equivalent? As far as I can tell, any usage of i <- ... outside of a for context causes an exception due to referencing an unknown value.


Solution

  • In short, yes <- is a reserved operator in Scala. It's a compiler thing.

    Foreach

    There is a strong distinction between foreach and for yield, but the syntax is only syntactic sugar, transformed at compile time.

    for (i <- 1 to 10) { statement } expression is translated to:

    Range.from(1, 10).foreach(..)
    

    Multiple variables: for (i <- 1 to 10; y <- 2 to 100) {..} becomes:

    Range.from(1, 10).foreach( el => {Range.from(2, 100).foreach(..)});

    With all the variations given by:

    for (x <- someList) = someList.foreach(..).

    Put simply, they all get de-sugared to foreach statements. The specific foreach being called is given by the collection used.

    For yield

    The for yield syntax is sugar for flatMap and map. The stay in the monad rule applies here.

    for (x <- someList) yield {..} gets translated to a someList.flatMap(..).

    Chained operations become hierarchical chains of map/flatMap combos:

    for { x <- someList; y <- SomeOtherList } yield {} becomes:

    someList.flatMap(x => { y.flatMap(..) }); and so on.

    The point

    The point is that the <- operator is nothing more than syntactic sugar to make code more readable, but it always gets replaced at compile time.

    To emphasize Rob's point

    Rob makes excellent examples of other Scala syntactic sugar.

    A context bound

    package somepackage;
    class Test[T : Manifest] {}
    

    Is actually translated to:

    class Test[T](implicit evidence: Manifest[T])

    As proof, try to alias a type with a context bound:

    type TestAlias[T : Manifest] = somepackage.Test // error, the : syntax can't be used.. It is perhaps easy to see how the : Manifest part is actually not a type a parameter.

    It's just easier to type class Test[T : Manifest] rather than class Test[T](implicit evidence: Manifest[T].