I can merge two lists as follow together:
List(1,2,3) ::: List(4,5,6)
and the result is:
res2: List[Int] = List(1, 2, 3, 4, 5, 6)
the operator ::: is right associative, what does it mean?
In math, right associative is:
5 + ( 5 - ( 2 * 3 ) )
Right associative means the operator (in our case, the ::: method) is applied on the right operand while using the left operand as the argument. This means that the actual method invocation is done like this:
List(4,5,6).:::(List(1,2,3))
Since ::: prepends the list, the result is List(1,2,3,4,5,6).
In the most general sense, right-associative means that if you don't put any parentheses, they will be assumed to be on the right:
a ::: b ::: c == a ::: (b ::: c)
whereas left-associative operators (such as +) would have
a + b + c == (a + b) + c
However, according to the spec (6.12.3 Infix Operations),
A left-associative binary operation e1 op e2 is interpreted as e1.op(e2). If op is rightassociative,
the same operation is interpreted as { val x=e1; e2.op(x ) }, where
x is a fresh name.
So right-associative operators in scala are considered as methods of their right operand with their left operand as parameter (as explained in #Yuval's answer).
Related
I am currently getting into Scala and am wondering about the difference it makes to use object notation when calling methods ending with ':'.
As a method name ending in ':' would normally produce right-side associativity, this seems to change when invocating such a method with object notation.
Example:
scala> 3 +: List(1,2)
res1: List[Int] = List(3, 1, 2)
scala> List(1,2) +: 3 // does not compile due to right side associativity
scala> (List(1,2)).+:(3)
res2: List[Int] = List( 3, 1, 2)
Now I do not understand why the right-associativity feature gets disabled by using object notation. Could someone explain this or link to the documentation on this issue?
From the spec, "Infix Operations":
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.
Method +: is defined on the List, which is why (List(1,2)).+:(3) works. Its implementation is such that the element is put in the front, so equivalent to 3 :: List(1, 2), but that's irrelevant here.
Using infix notation List(1,2) +: 3 won't work, because (as stated in the spec) all infix operators ending with a colon are right-associative, which means that the "right hand side" is using the operator with "left hand side" as a parameter, instead of vice-versa.
Basically,
1 +: 2 +: 3 +: List(4, 5)
and
List(4, 5).+:(3).+:(2).+:(1)
have identical effect (I know that has been pretty much obvious in your question already, but I'm just emphasizing the right-associativity).
So, to answer your question in one simple sentence: it's not that right-side associativity is removed in object notation, it's more that it's added in the infix notation, but only for methods ending with a colon.
What precisely is the difference between . and when used to invoke functions from objects in Scala?
For some reason, I get variations, like:
scala> val l:List[Int] = 1::Nil
l: List[Int] = List(1, 2, 3)
scala> l foldLeft(0)((hd, nxt) => hd + nxt)
<console>:13: error: Int(1) does not take parameters
| foldLeft(1)((hd, nxt) => hd + nxt)
^
scala>l.foldLeft(0)((hd, nxt) => hd + nxt)
res2: Int = 2
(And while I'm at it, what's the name of that operation? I kept trying to find the strict definition of the . operator and I have no idea what it's called.)
Having space instead of dot is called postfix notation if there are no arguments in the called function on the object, or infix notation if there is an argument that the function requires.
Postix example: l sum, equivalent to l.sum
Infix example: l map (_ * 2), equivalent to l.map(_ * 2)
The issue with these notations is that they are inherently more ambiguous in their interpretation. A classic example from math:
1 + 2 * 3 + 4 is ambiguous and depends on the priority of the operators.
1.+(2.*(3).+(4) has only one meaningful interpretation.
Therefore it is not a different operator, but the same as the dot, just susceptible to ambiguity that can lead to syntactical errors like your case or even worse logical errors when you chain infix operators.
You can actually express foldLeft with infix notation in this way:
(l foldLeft 0)((hd, nxt) => hd + nxt)
or even
(0 /: l)((hd, nxt) => hd + nxt)
Where /: is just an alias for foldLeft and makes use of the unique semantics of operator ending in colon(:), which are interpreted as l./:(0) (the reverse of the usual).
Desugar it with "-Xprint:parser" or "-Xprint:typer"
Example 1 Desugared:
scala> (List(1,2) foldLeft 0)((hd, nxt) => hd + nxt)
...
List(1, 2).foldLeft(0)(((hd, nxt) => hd.$plus(nxt)))
...
immutable.this.List.apply[Int](1, 2).foldLeft[Int](0)(((hd: Int, nxt: Int) => hd.+(nxt)));
As you can see, (List(1,2) foldLeft 0) translates into (List(1, 2).foldLeft(0)) in the parser phase. This expression returns a curried function that takes in the second set of parenthesis to produce a result (remember that a curried function is just a function that takes in an argument and returns another function with one fewer argument).
Example 2 Desugared:
scala> List(1,2) foldLeft(0)((hd, nxt) => hd + nxt)
...
List(1, 2)(foldLeft(0)(((hd, nxt) => hd.$plus(nxt))))
...
<console>:8: error: not found: value foldLeft
List(1,2) (foldLeft(0)((hd, nxt) => hd + nxt))
The parenthesis are going around (foldLeft(0)((hd, nxt) => hd + nxt)).
Style:
The way you are supposed to use space delimited methods is 1 object followed by 1 method followed by 1 set of parenthesis, which produces a new object that can be followed by a new method.
obj method paramerer // good
obj method1 paramerer1 method2 paramerer2 // good
obj method1 paramerer1 method2 paramerer2 method3 paramerer3 // compiles, but might need to be broken up
You can follow an object with postfix a method that takes no parameters, but this isn't always the approved style, especially for accessors.
foo.length // good
foo length // compiles, but can be confusing.
Space delimited methods are normally reserved for either pure functions (like map, flatmap, filter) or for domain specific languages (DSL).
In the case of foo.length, there is no () on length, so the whitespace isn't necessary to convey the idea that length is pure.
Why does foldLeft syntax operator works, for example i would expect this code
(10 /: (1 to 5))(_ + _)
To give me an error "value /: is not a member of Int". How does it expands method /: on all types in type system?
Here is the definition of the "shortcut" operator:
def /:[B](z: B)(op: (B, A) => B): B = foldLeft(z)(op)
If operator ends with a colon, it is a right-associative. 1 :: Nil is another example, there is no method :: on Int
this all works:
(1 to 5)./:(10)(_ + _)
((1 to 5) foldLeft 10)(_ + _) (almost the same as your example,
but here it's more obvious that foldLeft is actually a method on the
range object)
(1 to 5).foldLeft(10)(_ + _)
Your question is not entirely clear (there's no n mentioned in your expression), but: Operators that end with a colon are interpreted as methods on the right-hand argument, not the left. Your expression is equivalent to
(1 to 5)./:(10)(_ + _)
in which /: is more clearly seen to be a method of the Range object on the left.
I am currently learning about category theory in scala and the law of associativity says
(x + y) + z = x + (y + z)
Thats just fine when working with more than two values
("Foo" + "Bar") + "Test" == "Foo" + ("Bar" + "Test") // true
In that case order doesn't matter. But what if there are only two values. In case for numbers its still working ( commutative ) but when doing the same thing with strings it fails.
3+1==1+3 // True
("Foo" + "Bar") == ("Bar" + "Foo") // Not commuative
So is it legal to say that associativity requires commudativity to fullfill the monoid law ? And so is a String Monoid valid anyway ?
So is it legal to say that associativity requires commutativity to fulfill the monoid law?
No. A binary operation does not need to be commutative to be associative. The fact that ("Foo" + "Bar") == ("Bar" + "Foo") is false is irrelevant to the fact that + is associative for String.
And so is a String Monoid valid anyway?
Yes, you can have a Monoid[String].
By definition:
A monoid is a set that is closed under an associative binary operation +and has an identity element I in S such that for all a in S, I + a = a + I = a.
A monoid must contain at least one element.
Let + be the binary operation for a Monoid[String]. For any two strings, a and b, a + b is also a String, so the binary operation is closed over the type String. Without rigorous proof, we also know that it is associative.
i.e. for all strings a, b, and c:
(a + b) + c == a + (b + c)
We also have an identity element "" (the empty string), because for any string a, a + "" == a and "" + a == a.
A monoid whose binary operation is also a commutative is called a commutative monoid. And you clearly cannot have a commutative monoid for String using the + operation.
See this:
scala> 1 + 1
res0: Int = 2
scala> 1.+(1)
warning: there were 1 deprecation warning(s); re-run with -deprecation for details
res1: Double = 2.0
scala> "a" :: List()
res2: List[String] = List(a)
scala> "a".::(List())
<console>:8: error: value :: is not a member of String
"a".::(List())
^
Why does the error occur?
Try this
List().::("a")
The reason is that :: is a method of List.
From ScalaByExample:
Like any infix operator, :: is also implemented as a method of an
object. In this case, the object is the list that is extended. This is
possible, because operators ending with a ‘:’ character are treated
specially in Scala. All such operators are treated as methods of their
right operand. E.g.,
x :: y = y.::(x) whereas x + y = x.+(y)
Note, however, that operands of a binary operation are in each case
evaluated from left to right. So, if D and E are expressions with
possible side-effects,
D :: E
is translated to
{val x = D; E.::(x)}
in order to maintain the left-to-right order of operand evaluation.
In scala methods which ends with : got applied in reverse order.
So when you write a::list it is actually list.::(a). String doesn't have :: method, so the solution is to write List().::("a") or Nil.::("a")
Because of operator precedence. In Scala methods which ends with : are right associative. So you should call List().::("a")
If you want to use left associative method then you should write List("a") ++ List(), but that's not always a good choice, cause it has linear execution time