Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Consider this method signature:

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.


The difference is that Scala also abstracts over the "container" not just the contents.

If Scala only did what ML and Haskell do, the type signatures would be exactly that.


> The difference is that Scala also abstracts over the "container" not just the contents.

Haskell is perfectly able to do that as well if desired. `[]` is a monadic type.


Haskell has no subtyping as far as I know Haskell.


Haskell has typeclasses which would probably correspond to structural typing in Scala. This type signature:

Functor f => (a -> b) -> f a -> f b

Defines map for any type that implements the type class functor. Lots of types will implement functor and it is easy to implement it yourself.


> 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).


You get typeclasses in Scala via an interaction between traits and implicits. See http://www.sidewayscoding.com/2011/01/introduction-to-type-c... for a super-quick overview and a link to a more comprehensive paper.



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.


Are you hinting at Haskell's fmap? The type signature is significantly simpler:

fmap :: Functor f => (a -> b) -> f a -> f b


That doesn't capture the type of the original example.

    BitSet(1, 2, 3).map { _.toString + "!" } // returns Set[String]
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.


Thanks for explaining. I now understand what the OP meant by "automatic conversions".


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:

  def map [B] (f: (A) ⇒ B): Traversable[B]

  def map [B, That] (f: (A) ⇒ B)(implicit bf: CanBuildFrom[Repr, B, That]): That
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.

[0]: http://www.scala-lang.org/api/current/index.html#scala.colle...

[1]: http://stackoverflow.com/questions/1721356/scala-2-8-canbuil...


     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).


> Bullshit. You just followed somebody else's public explanation.

If you're referring to the stack overflow answer, then no. I understood the signature by learning Scala (by reading a book). Is that cheating?

> Does that mean that monad comprehensions are also beginner friendly?

No, not necessarily. Do you have to understand what that even means to be able to use Scala?


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.


You have a reading comprehension deficiency.

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 ;)




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: