You know that scene in Goodbye and Thanks For All The Fish where Arthur is so deliriously happy he stops a waiter and demands to know, "Why is this food so good?" I'm in that situation. Scala seems to be doing exactly what I'd want, but I don't understand how it's doing it. Consider the following:
scala> var v = Nil:List[String];
v: List[String] = List()
scala> v.length
res38: Int = 0
scala> v ::= "Hello"
scala> v.length
res39: Int = 1
scala> Nil.length
res40: Int = 0
Which is exactly what you'd hope for, but how is it happening?
Nil is an object that extends List[Nothing], which is a subtype of List[String], so assignment works fine, but it's an immutable list, isn't it? So I shouldn't be able to append to it. But I can append to it, or at least I can append to v, which I thought point to Nil. v is changed, but Nil isn't.
So, WTF? Does Scala have some clever copy-on-modify semantics I'm unaware of? Is Nil really a function that returns empty lists? More broadly, is there some way I could have made the REPL answer these questions?
When Scala sees
v ::= "Hello"
it first checks if v knows the method ::=. In this case (type List), it doesn't, so, because the method consists only of symbols and ends with an = sign, it tries something else:
v = v.::("Hello")
which, incidentally, is written "Hello" :: v in infix notation. Now, since v knows the method ::, that works and returns a new List, which is then assigned to v as indicated.
By the way, it is prepending, not appending.
When you use the keyword var you're not "translating to final" in the Java sense. Instead, var allows for reassignment. When you call the operator ::= you're actually reassigning what v points to. The operator ::= returns a new list, not the original list with "Hello" appended to it. Hence, v is now pointing to "Hello" and not a Nil list.
Here, watch this:
var myThing = new List(1, 2, 3)
var myOhterThing = myThing
myThing = new List(1, 2, 3, 4)
It's almost the same as saying "take the first list, copy it and append a '4' onto it. Now assign 'myThing' to point to that list." Using that operator you have effectively done the same thing. Writing it out this way lets you see that.
Try it with val v, to see what's mutable. :)
Nil is immutable. When you cons to it (::) you get a new instance which is also immutable.
Try this:
val v1 = Nil
val v2 = "Hello" :: v1
v1 == v2
(you will get false since they don't point to the same object)
Given v is a var you can reassign values to it.
So when you have:
v ::= "Hello" // what you really have is:
v = "Hello" :: v // or
v = "Hello" :: Nil // or
v = List("Hello")
The above creates a new List and Nil is left unchanged. It is similar to String addition in Java. Since String is immutable you never change a single instance - only create new ones.
Since Nil has not changed, Nil.length = 0.
Related
Sorry, I got even no idea how to name a title, as I believe this is a dead simple thing.
I have function which is taking Int as an argument and returning List of Ints (after adding values to int trough loop, and couple if statements. Signature is a must)
My problem:
def a(i:Int) = { var l2 = List(1,2); l2.+:(1); l2; }
println(a(3)) // outputs List(1, 2)
Why function a is returning List(1,2) instead of List(3,1,2) ??
And what would be a correct solution in this situation?
I really appreciate your time to help me.
The method +: that you are calling on a List returns a new list with the element prepended. The original list is not modified.
In the statement l2.+:(1) you are ignoring the return value (the new list with the element prepended). Then you return l2, which still refers to the original List which contains the two elements 1 and 2.
You are missing an assignment (and you probably meant i instead of 1): l2 +:= i.
But in scala it is preferable to avoid using var:
def a(i: Int) = { i :: List(1, 2) }
(I guess your method is much more complex, but it's almost always possible to rewrite it that way)
I am new to Scala and studying a book about it (Programming in Scala). I am really lost, what is the author trying to explain with the code below. Can anyone explain it in more detail ?
{ val x = a; b.:::(x) }
::: is a method that prepends list given as argument to the list it is called on
you could look at this as
val a = List(1, 2)
val b = List(3, 4)
val x = a
b.prependList(x)
but actually for single argument methods if it's not ambiguous scala allows to skip parenthesis and the dot and this is how this method is supposed to be used to not look ugly
x ::: b
it will just join these two lists, but there is some trick here
if method name ends with : it will be bound the other way
so typing x ::: b works as if this type of thing was done (x):::.b. You obviously can't type it like this in scala, won't compile, but this is what happens. Thanks to this x is on the left side of the operator and it's elements will be on the left side (beginning) of the list that is result of this call.
Oh well, now I found maybe some more explanation for you and also the very same piece of code you posted, in answer to this question: What good are right-associative methods in Scala?
Assuming a and b are lists: It assigns a to x, then returns the list b prepended with the list x.
For example, if val a = List(1,2,3) and val b = List(4,5,6) then it returns List(1,2,3,4,5,6).
Sorry guys, I recently saw an example in "Programming in Scala", 2nd Edition on page 685, which seemed strange to me:
var hashSet: Set[C] = new collection.immutable.HashSet
hashSet += elem1
How is it possible to add something an immutable collection? I tried on REPL and it worked ok!
> scala
Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_11).
Type in expressions to have them evaluated.
Type :help for more information.
scala> var s : Set[Int] = collection.immutable.HashSet()
s: Set[Int] = Set()
scala> s += 1324
scala> println(s)
Set(1324)
The stranger fact is that += operator is not defined in immutable.HashSet api page. Could anybody please help me understand what's going on?
Thanks.
You are not adding to the HashSet. You are assigning to hashSet, which is perfectly fine, since hashSet is a var, not a val.
Section 6.12.4 Assignment Operators of the Scala Language Specification (SLS) explains how such compound assignment operators are desugared:
l ω= r
(where ω is any sequence of operator characters other than <, >, ! and doesn't start with =) gets desugared to
l.ω=(r)
iff l has or is implicitly convertible to an object that has a member named ω=.
Otherwise, it gets desugared to
l = l.ω(r)
(except l is guaranteed to be only evaluated once), if that typechecks.
This allows something like += to work like it does in other languages but still be overridden to do something different.
Observe this:
scala> var hashSet: Set[Int] = new collection.immutable.HashSet
hashSet: Set[Int] = Set()
scala> val set2 = hashSet + 1234
set2: scala.collection.immutable.Set[Int] = Set(1234)
scala> set2
res20: scala.collection.immutable.Set[Int] = Set(1234)
scala> hashSet
res21: Set[Int] = Set()
So nothing gets added to the immutable hashSet. hashSet is the same as it was when constructed. + returns a new set altogether and the original set is unchanged.
When you do hashSet += 1234, it is a scala shorthand for (note no method += exists in HashSet):
val temp = hashSet + 1234
hashSet = temp
+= will work for any class which follows this protocol. In short when you do a += 12. a must have a method + which returns the same type as a's and a should be assignable (i.e. a var. It does not work for val. Try this: val i = 23; i+=1).
Short answer
You have a var so you can reassign to it. So += in this case will be translated to
hashSet = hashSet + elem
just like other types, as long as + is defined on them
var i = 0
i += 1
i = i + 1
Details
immutable.HashSeth has + method which
Creates a new set with an additional element, unless the element is
already present.
according to docs.
There is no += method defined in this class, so += will be a synthetic method given to you by compiler which acts as an operator simply calling the + method on the left operand by passing the right operand and assigning the result back to the left operand.
So I assume my issue has to do with what is going on under the hood, but I don't understand why this doesn't works:
scala> b = b :: "apple";
<console>:8: error: value :: is not a member of java.lang.String
but this does:
scala> b = "apple" :: b;
b: List[java.lang.String] = List(apple, pear)
Thanks.
Method names that end in : are right associative, so b :: "apple" tries to call the :: method on a String, which doesn't exist.
The normal strategy for appending things if you must use a List is to add things to the beginning then reverse the result when you're done. But as Rex says, using a Vector might be better.
:: always joins a new item to the head of a list. Adding an item to the end can be done, but it takes time proportional to the length of the list (since the entire list must not only be traversed but, actually, also rebuilt).
If you really must add a item to the end of a list, use :+:
List("pear","orange") :+ "apple"
Better yet, use Vector when you need to add to the end (it's much faster at double-ended additions):
Vector("grape","peach") :+ "apple"
Another possibility is to explicitly add Nil to the end of chain:
scala> val a = "apple"
a: java.lang.String = apple
scala> val b = "pear"
b: java.lang.String = pear
scala> a::b::Nil
res0: List[java.lang.String] = List(apple, pear)
I'd like to use val to declare multiple variable like this:
val a = 1, b = 2, c = 3
But for whatever reason, it's a syntax error, so I ended up using either:
val a = 1
val b = 2
val c = 3
or
val a = 1; val b = 2; val c = 3;
I personally find both options overly verbose and kind of ugly.
Is there a better option?
Also, I know Scala is very well thought-out language, so why isn't the val a = 1, b = 2, c = 3 syntax allowed?
The trivial answer is to declare them as tuples:
val (a, b, c) = (1, 2, 3)
What might be interesting here is that this is based on pattern matching. What is actually happens is that you are constructing a tuple, and then, through pattern matching, assigning values to a, b and c.
Let's consider some other pattern matching examples to explore this a bit further:
val DatePattern = """(\d{4})-(\d\d)-(\d\d)""".r
val DatePattern(year, month, day) = "2009-12-30"
val List(rnd1, rnd2, rnd3) = List.fill(3)(scala.util.Random.nextInt(100))
val head :: tail = List.range(1, 10)
object ToInt {
def unapply(s: String) = try {
Some(s.toInt)
} catch {
case _ => None
}
}
val DatePattern(ToInt(year), ToInt(month), ToInt(day)) = "2010-01-01"
Just as a side note, the rnd example, in particular, may be written more simply, and without illustrating pattern matching, as shown below.
val rnd1, rnd2, rnd3 = scala.util.Random.nextInt(100)
Daniel's answer nicely sums up the correct way to do this, as well as why it works. Since he already covered that angle, I'll attempt to answer your broader question (regarding language design)...
Wherever possible, Scala strives to avoid adding language features in favor of handling things through existing mechanisms. For example, Scala doesn't include a break statement. However, it's almost trivial to roll one of your own as a library:
case object BreakException extends RuntimeException
def break = throw BreakException
def breakable(body: =>Unit) = try {
body
} catch {
case BreakException => ()
}
This can be used in the following way:
breakable {
while (true) {
if (atTheEnd) {
break
}
// do something normally
}
}
(note: this is included in the standard library for Scala 2.8)
Multiple assignment syntaxes such as are allowed by languages like Ruby (e.g. x = 1, y = 2, z = 3) fall into the category of "redundant syntax". When Scala already has a feature which enables a particular pattern, it avoids adding a new feature just to handle a special case of that pattern. In this case, Scala already has pattern matching (a general feature) which can be used to handle multiple assignment (by using the tuple trick outlined in other answers). There is no need for it to handle that particular special case in a separate way.
On a slightly different aside, it's worth noting that C's (and thus, Java's) multiple assignment syntax is also a special case of another, more general feature. Consider:
int x = y = z = 1;
This exploits the fact that assignment returns the value assigned in C-derivative languages (as well as the fact that assignment is right-associative). This is not the case in Scala. In Scala, assignment returns Unit. While this does have some annoying drawbacks, it is more theoretically valid as it emphasizes the side-effecting nature of assignment directly in its type.
I'll add one quirk here, because it hit myself and might help others.
When using pattern matching, s.a. in declaring multiple variables, don't use Capital names for the variables. They are treated as names of classes in pattern matching, and it applies here as well.
val (A,B)= (10,20) // won't work
println(A)
Error message does not really tell what's going on:
src/xxx.scala:6: error: not found: value A
val (A,B)= (10,20)
^
src/xxx.scala:6: error: not found: value B
val (A,B)= (10,20)
^
src/xxx.scala:7: error: not found: value A
println(A)
^
I thought `-ticking would solve the issue but for some reason does not seem to (not sure, why not):
val (`A`,`B`)= (10,20)
println(A)
Still the same errors even with that.
Please comment if you know how to use tuple-initialization pattern with capital variable names.
If all your variables are of the same type and take same initial value, you could do this.
val a, b, c: Int = 0;
It seems to work if you declare them in a tuple
scala> val (y, z, e) = (1, 2, 45)
y: Int = 1
z: Int = 2
e: Int = 45
scala> e
res1: Int = 45
Although I would probably go for individual statements. To me this looks clearer:
val y = 1
val z = 2
val e = 45
especially if the variables are meaningfully named.