Summary
After a conversation with a guy at work, I was inspired to take a look at whether Scala is inherently more complex than Java, and decided it wasn't.
Advertisement
Today, I was asked by a friend to provide an alternative to the following Java one-liner:
This is a one-liner that unsurprisingly prints out the happy birthday song. Easy enough. So, I set about doing something in scala. While at it, I had a little fun and made a few "improvements". The result was this:
(1 to 4).map { i => "Happy Birthday %s".format(if (i == 3) "Dear XXX" else "To You") }.foreach { println(_) }
When another friend saw this, he immediately opined that the Scala version was too complicated and would be hard to maintain. An examination, I feel, is clearly called for here.
Why does it look so wierd?
Well, there are a number of reasons for why this looks hard to understand. The most obvious one is unfamiliarity. We have had Java around for 15+ years now, and we all know the syntax really well here. I would dissect a couple of parts and examine them in isolation to illustrate.
for(int i=0; i<4; i++) {}
It's familiar to all of us, but really would we hold it up as a perfect example of readability. Someone with no C or Java experience might wonder what the statement actually does. We all know the first part is the initialization, the second is the termination test, and the third is the increment, but compare this with something like:
for i in 1 to 4 begin ... end
Which is what you would see in BASIC. Java has more than it's fair share of "lore", it's just we seem to forget about that.
Now Scala actually has several options for this kind of loop, perhaps a more fair comparison in my example would have been:
for (i <- 1 to 4) { }
(I tend to read <- as "from") so: for i from 1 to 4 - pretty readable and understandable, I would say moreso than the Java one, if you were cold on both languages. However, that's not the form I used, I instead used the more universal.
(1 to 4).map { }
I could have, more fairly, used
(1 to 4).foreach { }
Which I think is very readable - the reason for using map instead will be explored below, but if I was to write it using foreach directly, we could end up with:
(1 to 4).foreach {i => println ("Happy Birthday " + (if (i == 3) "Dear XXX" else "To You")) }
Alternatively, I could have used:
for (i <- 1 to 4) println ("Happy Birthday " + (if (i == 3) "Dear XXX" else "To You"))
Now, that's pretty reasonable, so why not stick with that version? Before we go on, I would like to point out that I also consider the ternary operator in Scala superior in readability to Java. Just compare:
i == 3 ? "A" : "B"
Kind of looks like line noise until you are used to it, now how about
if (i == 3) "A" else "B"
now that's more like it. Again, if you aren't familiar with either language, which is the easier to read? As an added bonus, there is no difference between the syntax of a typical if else statement and the ternary operator, I mean, they do the same thing, so why make them different?
So, given that, why didn't I stick with the simple, readable version. I could have easily done so, functionally it is directly equivalent to the Java version, and therein lies the (maybe a little belabored) point.
Most programs don't print stuff out to Standard Out these days, they might log it, but mostly what they do is prepare data to go into user interfaces. As such, the Java for loop is a bit useless. Let's see how we would actually go about making the sentences that make up the song in Java. I think the following would be the most compact and best signal to noise ratio:
String[] birthdayArray = new String[4];
for (int i = 0; i < 4; i++) { birthdayArray[i] = "Happy Birthday " + (i==2 ? "Dear XXX" : "To You"); }
(Most of the time you want something more durable like a list rather than an array for all sorts of good reasons, but you could just stop at an array if you wanted one).
We could put all of these statements onto one line of course, but I think readability would suffer more. The good news is, we could now use the list of strings to populate something in a GUI or web page with nice markup. This is more representative of what we do as developers every day.
What would this look like in Scala?
val birthdaySong = (1 to 4).map { i => "Happy Birthday " + (if (i==3) "Dear XXX" else "To You") }
Now we are starting to see the worm turn! The above is completely type-safe (don't be fooled, the compiler knows that birthdaySong contains a list of strings at the end of this), however we don't have to initialize a structure to hold the results, nor supply any type information since the compiler can infer it.
Look at the example again, and maybe you will start to see that the syntax looks less alien to you after a while. Certainly I think that the signal to noise ratio is higher than with the Java equivalent.
I also don't like that addition of strings. It works if you are adding something to the end of the string, but what if you want a bunch of fields in the middle. Here are the Java/Scala equivalents there.
not that different, but the importing of MessageFormat and the use of a static method is orthogonal to the functionality we actually want. It adds nothing to the readability of the code, it's just more noise.
So, let's see where we are now. From the original Java we have:
And while we know a direct scala equivalent can be written as:
for (i <- 1 to 4) println ("Happy Birthday " + (if (i == 3) "Dear XXX" else "To You"))
We have actually ended up with:
(1 to 4).map { i => "Happy Birthday %s".format(if (i == 3) "Dear XXX" else "To You")) }
Suddenly that's starting to look cleaner, and it is probably more useful code for the world of web applications or rich GUIs, but the original aim was to print something else, so now we have the birthday song verse as a list of strings, let's just print them out:
birthdaySong.foreach { println (_) }
The _ is just shorthand for "the thing" (the only value available when iterating over the list). We could also have done:
birthdaySong.foreach { line => println (line) }
Perhaps the second is a little more readable, perhaps not after you get just a little more comfortable with the syntax.
So where does that leave things? Well, for the original scala answer, the one that prompted the complexity comment, we have:
(1 to 4).map { i => "Happy Birthday %s".format(if (i == 3) "Dear XXX" else "To You") }.foreach { println(_) }
Abandoning the need to keep it all on one line, we get:
val birthdaySong = (1 to 4).map { i => "Happy Birthday %s".format( if (i == 3) "Dear XXX" else "To You" ) }
birthdaySong.foreach { println ( _ ) }
In Java, even using String array as an intermediate, we get:
String[] birthdayArray = new String[4];
for (int i = 0; i < 4; i++) { birthdayArray[i] = MessageFormat.format("Happy Birthday {0}", i==2 ? "Dear XXX" : "To You"); }
for (String line : birthdayArray) { System.out.println(line); }
And for the regular old just print it out, we have (Scala first):
for (i <- 1 to 4) println ("Happy Birthday " + (if (i == 3) "Dear XXX" else "To You"))
and Java:
for (int i = 0; i < 4; i++) { System.out.println("Happy Birthday " + (i == 2 ? "Dear XXX" : "To You")); }
Can you still say Scala is more complex based on these examples? Is it not possible that it is just different?
Nice post Dick. You did not mention the shortest way of printing out all elements of a list the foreach(println) way. Do you think that is confusing to new Scala devs?
Personally I think foreach(println) reads very well.
> Of course, the best language would be perl right since > we're outputting human readable text. > > $name = "Dick Wall" > print <<ENDOFTEXT > Happy Birthday to you > Happy Birthday to you > Happy Birthday dear $name > Happy Birthday do you > ENDOFTEXT > > My point being that neither Java nor Scala solve the > problem the way it probably should be solved :) > Scala can come a bit close to your approach:
val name = "Dick Wall" println(""" Happy Birthday to you Happy Birthday to you Happy Birthday dear """ + name + """ Happy Birthday do you """)
What's missing is the string interpolation feature. But when you run this you get:
Happy Birthday to you Happy Birthday to you Happy Birthday dear Dick Wall Happy Birthday do you
> I'd consider both neutral with Scala being a bit more perl > like in that we have our magic '_' thing (what does it > mean again? is it 'this?') > The underscore has a few different meanings, but mostly they all mean some kind of "blank" to be filled in. The main use of it is as a placeholder for an argument passed to a function. Instead of saying:
List(1, 2, 3).map(n => n + 1)
You can write:
List(1, 2, 3).map(_ + 1)
Either of these will give you List(2, 3, 4), so in this case the blank gets filled in with the argument to the function.
>> val birthdaySong = (1 to 4).map { i => "Happy Birthday " >> + (if (i==3) "Dear XXX" else "To You") }
>> Now we are starting to see the worm turn! >> The above is completely type-safe (don't be fooled, >> the compiler knows that birthdaySong contains a list >> of strings at the end of this), however we don't have to >> initialize a structure to hold the results, nor supply any >> type information since the compiler can infer it.
I don't think, that it is so cool, if the compiler infers the type. The code is easier to write but more difficult to read and maintain. I have to read the whole line in order to understand, that the birthdaySong is a map of Strings. Suddenly you have all the variables with unknown types popping up. That is a sloppy way of programming. I wouldn't encourage it by making it possible through some language features.
Nice post. There are many more reasons why Scala isn't inherently more complicated, but this is a nice start.
I would personally create a single string with mkString("\n") and then print it. If we imagine that println was asynchronous, then this would also ensure that the lines would print in the right order.
As for the printing, I don't even see the need for that in the scala console or on simplyscala.com, where the interpreter is showing you what's being evaluated at each step.
> I would personally create a single string with > mkString("\n") and then print it. If we imagine that > println was asynchronous, then this would also ensure that > the lines would print in the right order. >
The synchronicity of println isn't relevant (messages going from one actor to another actor are usually guaranteed to arrive in order if they arrive). The risk here is that foreach (note: am I the only one who thinks a method called each reads infinitely better than one called foreach?) evaluates each item in parallel (à la pmap), and it doesn't.
> I don't think, that it is so cool, if the compiler infers > the type. The code is easier to write but more difficult > to read and maintain.
Experience says it usually isn't, and tools can display the inferred types pretty easily.
> I have to read the whole line in > order to understand, that the birthdaySong is a map of > Strings.
Given it's a sequence, you apparently didn't read it to the end.
> Suddenly you have all the variables with unknown > types popping up.
There are no unknown types anywhere. If there were, it wouldn't compile in the first place.
Like it or not functional programming, no matter how wonderfully versatile you think closures are, or how foldl makes you feel warm and cuddly, is more complicated.
It's more complicated because 1) It can use many, many ways to do the same thing, and all of them have the same affordability mostly. 2) Shifts the emphasis to data structure manipulation instead of primitive (or user made) types manipulation. 3) It compresses meaning so even the shortest n-grams can alter a program significantly (not that this couldn't be made with c or java for that matter, but you would need to really try, weather with scala and haskell it seems almost accidental). So you have to have a laser steel tipped intellect just so you are sure what a piece of code of doing.
@Morel yes, my general idea was that it's easier to make a program concurrent if there's no explicit iterating over sequences. About actors, you're only correct if you're sending to the same actor (since it implements a message queue). If you're sending each message to a different actor, there's a race condition.
The beauty of Scala is that you can use a more imperative style or a more functional style and transition from the former to the latter. For instance, the same example might be also expressed like this with just functional transformations:
(List.make(4, "Happy birthday ") zip (List.tabulate(4, { case 2 => "dear friend" case _ => "to you"})) ) map (Function.tupled(_+_)) mkString ("\n")
Not sure this is more readable by the average Java programmer, so we're probably digressing from the topic of the article.
> Yes. > > Like it or not functional programming, no matter how > wonderfully versatile you think closures are, or how foldl > makes you feel warm and cuddly, is more complicated. >
It's not.
> It's more complicated because > 1) It can use many, many ways to do the same thing, and > all of them have the same affordability mostly.
That's not specific to FP.
> 2) Shifts the emphasis to data structure manipulation > instead of primitive (or user made) types manipulation.
That's just a different way of thinking about programs than the one you're accustomed to, it's not inherently more complicated.
> 3) It compresses meaning so even the shortest n-grams can > alter a program significantly (not that this couldn't be > made with c or java for that matter, but you would need to > really try, weather with scala and haskell it seems almost > accidental). So you have to have a laser steel tipped > intellect just so you are sure what a piece of code of > doing.
There's nothing specific to FP languages either here. That's a property of some languages mostly orthogonal to their main paradigm: Scheme doesn't tend to expose such a behavior, Perl does. The former is a functional language, the latter isn't.
Flat View: This topic has 54 replies
on 4 pages
[
1234
|
»
]