def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That
All this complexity to allow for automatic conversions (the following will return a Set[String]) ...
BitSet(1, 2, 3).map { _.toString + "!" }
In a dynamic language, the signature of map is simply this: (a → b) → [a] → [b]
The big difference is that the language doesn't care about types "a" and "b" until runtime. You're the one that cares about it, only on a need-to-know basis. Hence the implicit conversions become explicit (although conversions are less necessary) and code correctness is guarded by unit tests, which you need with Scala anyway. Also, the implementation of "map" in a dynamic language is something that a junior developer can come up with.
So the biggest problems I have with Scala:
1) it is not readable
2) it separates developers in two camps:
library designers and users
3) it does not provide clear benefits
over dynamic languages (Haskell does)
Not only dynamically typed languages, the signature would also look remarkably close to (a → b) → [a] → [b] in ML and Haskell. Your ML and Haskell compiler would also make check that it was called correctly at compile time without extra unit tests.
> Haskell has typeclasses which would probably correspond to structural typing in Scala.
Nah, typeclasses are nominative typing but added post-facto (you can define a typeclass instance for a third party's type). Typeclasses are similar to Scala's traits I think (I don't know if you can add traits to a library's types though).
It does, but it does not support downcasting (it does support upcasting)
For instance, the Ord typeclass extends the Eq typeclass, so any instance of Ord can be used as an instance of Eq. But there is no way to cast an instance of Eq to an instance of Ord.
The Scala code here starts with one Functor (BitSet) and returns a different Functor (Set), depending on the type of the function (in this case, Int => String). If the function had type (Int => Int), it would still return a BitSet.
The signature for fmap does not encode this flexibility.
Can you explain the scala type signature for map? I see no container declared and it is less than obvious to me how it actually represents map across a container. I guess it is the implicit?
I'm a Scala newbie, and it did not take me long to actually understand the type signature here. Of course, when I first saw it I was confused, but it's not magical.
There are probably map-methods elsewhere, but let's take the map defined on TraversableLike[0]. TraversableLike is a base trait for all kinds of Scala collections (let's just pretend trait is like an interface for now, look it up if you're interested in the details).
TraversableLike has two definitions of map in 2.9.1:
The first one isn't so bad, is it? The first one takes a function converting objects of A to objects of B, and returns something that is traversable of B (i.e. perhaps a List[B] or Set[B]).
The second one can return anything you want, as long as you have a function that describes how the objects can be converted. That is what the implicit is for. For instance, if you map a collection over a function that returns a list of integers, but you actually wanted a set of strings - you can define a function that converts your List[Int] to Set[String]. I recently found a description of canBuildFrom on Stack Overflow that explains this in a bit more detail[1].
As a Scala beginner I find that I often can't expect to understand everything at once - a lot of times it's like other languages, but when it's not I usually can't just guess what it does, but actually have to take time to learn it. I don't think this is a bad thing.
it did not take me long to actually understand
the type signature here
Bullshit. You just followed somebody else's public explanation. Not a problem per se, but this doesn't strike me as beginner friendly ... Dr. Brian Beckman also has a great description of monads on Channel9 that doesn't involve category theory. Does that mean that monad comprehensions are also beginner friendly?
if you map a collection over a function
that returns a list of integers, but you
actually wanted a set of strings
Here's some Ruby code for you:
[1,2,3].map{|x| x * 2}.map{|x| x.to_s}.to_set
Ironically I find this code written in a dynamic language to be much more readable. Also, give me first-class functions and recursion in a dynamic language and I can implement all of the above by my own (lists, sets and map).
How about acting less aggressive?
It certainly dosen't help you to get your point across.
> Here's some Ruby code for you
You just tried very hard to not understand the problem at hand, right?
Because the equivalent Scala code looks like this:
Array(1,2,3).map(_*2).map(_.toString).toSet
> I can implement all of the above by my own
Sure, if you OK with your code running magnitudes slower than the built-in stuff.
That's probably the main difference between general purpose languages and scripting languages today: General-purpose languages allow you to implement the appropriate data structures yourself, while scripting languages force you to use the built-in stuff.
The issue discussed here is the implementation and signature of "map", a trivial function that anybody should be able to understand and implement without a sweat.
Sure, if you OK with your code running magnitudes
slower than the built-in stuff
...
scripting languages force you to use the built-in stuff
These 2 sentences don't compile.
You should also realize that the speed of the JVM has nothing to do with the static-ness of the language. The bytecode is in fact dynamic ... the only instances where certain assumptions where made based on Java (the language) being:
1) classes are immutable and can't be garbage collected unless you destroy the corresponding class loader
2) the method dispatching done cannot be overridden (so when calling a method on an implicit object, you also have to specify the interface where that method is defined) ... but this has nothing to do with the inner-workings of the JVM and is being addressed with the work done on InvokeDynamic
Really, if you're worrying about "scripting" specific behavior sneaking in on you, stealing away precious CPU cycles or RAM bytes, then you should stay away from the JVM.
Seeing that you still don't grok the basics discussed here, this is probably my last answer to you.
You still fail to understand that the `map` (and other methods) are far more flexible in Scala, making them play well with sub-typing and inheritance (and in fact with stuff like `String` and arrays which don't implement any collection methods at all).
With your examlpe above, what result collection type would you get when you map with `Int -> String` over a `BitSet` or with `Char -> Int` over a `String`?
> You should also realize that the speed of the JVM has nothing to do with the static-ness of the language.
You have obviously never seen the mess JRuby and Clojure employ to run on the JVM.
The example above was made by me, on purpose to explain the difference, which I did.
On the JVM - I've implemented dozens of parsers and simple compilers, I did a lot of bytecode manipulation. I know my bytecodes, I know very well the challenges involved.
Your oppinion is based on gospel and the authors of Clojure or JRuby dissagree with you ;)
def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That
All this complexity to allow for automatic conversions (the following will return a Set[String]) ...
BitSet(1, 2, 3).map { _.toString + "!" }
In a dynamic language, the signature of map is simply this: (a → b) → [a] → [b]
The big difference is that the language doesn't care about types "a" and "b" until runtime. You're the one that cares about it, only on a need-to-know basis. Hence the implicit conversions become explicit (although conversions are less necessary) and code correctness is guarded by unit tests, which you need with Scala anyway. Also, the implementation of "map" in a dynamic language is something that a junior developer can come up with.
So the biggest problems I have with Scala: