Wednesday, December 17, 2008

Scala over Ruby

In this post, I'll be maintaining a list of things I like more about Scala, over Ruby. I am also maintaining a similar, opposite list - Ruby over Scala. I plan on maintaining this list here and adding more to it over time, as opposed to several posts. I'm not exactly sure how that works with feeds and things, but oh well.

Also, if at any point I'm totally wrong (which is very possible because I'm new to Ruby), please let me know! It's likely that I just didn't know I could do something in Ruby. I'm eager to learn, and for new ideas, and I'll be more than happy to update the post. Anyway, Onward...


1. Scala doesn't make you use the "." operator when calling methods.

This one I'll try to explain using some simple examples. Here is some Ruby code:

x = 5
y = 6
z = x + y
z2 = x.+y

class ClassWithPlusMethod
def +(other)
# do something, doesnt matter what...
return "whatever"
end
end

x = ClassWithPlusMethod.new
y = ClassWithPlusMethod.new
z = x + y
z2 = x.+y

class SomeOtherClass
def plus(other)
# do something, doesnt matter what...
return "whatever"
end
end

x = SomeOtherClass.new
y = SomeOtherClass.new
z = x plus y

(irb):25: warning: parenthesize argument(s) for future version
NoMethodError: undefined method `plus' for main:Object
from (irb):25

z2 = x.plus y # this is ok!

As you can see, Ruby has magic handling of + mathematical "operator" (and others). "+" is a simple method call, but since it's name is +, Ruby doesn't require the dot, its interpreter handles it differently. This magic is built into the language, and does not extend to methods with regular names, as shown above with "plus".

In Scala, the . operator is not mandatory on any method call. This may seem trivial, but it is not. It helps in creating cleaner DSL's. Take for example, my post on Equalizer.

In Scala, I was able to add the mustBe method to Any, and call it very nicely. But, in Ruby, I had to put in the dot. Contrast these examples:

x mustBe 5

vs

x.must_be 5


2. Method overloading is good.



I use method overloading a lot. An awful lot. I love it. I don't think I need to explain it however.

3. Scala's apply method.



Scala's apply method is something that I'm pretty sure just doesn't exist at all in Ruby, and I'm not sure it can be done easily. For those who don't know what it is, I'll explain, and maybe some Rubyists can show some similar examples. Given that I'm still not a Ruby expert, I could be totally wrong. If so, I would like to know.

For any method called apply, ".apply" can be omitted from the method call. For example given the following definition of the class Array:


class Array{
def get(index:Int) = { ...some code to get from the array... }
def apply(index:Int) = get(index)
}

And an instance of that class:

val a = new Array(whatever)

Then the following calls are essentially equivalent:

a.get(7) // though only because apply calls get
a.apply(7)
a(7)


This is really useful in cleaning up syntax. However, the real beauty of it is hidden below the surface, and will be the subject of a future post.

4. Ruby faking keyword parameters to methods



Calling methods with keyword params is nice for client code readability. For example:

c = httpConnection( :host => "google.com", :port => 80 )

However, in Ruby the niceness ends at the client code. The library code to support this is abysmal. The reason being, for the method using those parameters, its not at all obvious what the method requires. You have to dig down into the implementation to find out what it actually pulls out of params. This is not ok with me. When I look at a method signature, I should be able to understand what dependencies the method has. Example:

def httpConnection(params)
...
...
port = params[:port]
...
...
host = params[:host]
end


5. 1 vs. 3 line method defs.



I run into this one a lot, and find that Ruby code ends up being a lot larger than Scala code for this reason. In Ruby, I can't define a method on one line without it looking simply terrible. Example:

def add7(x)
x + 7
end

or

def add7(x); x + 7; end

The first version is 3 lines when it doesn't need to be. The second version is one line littered with noise.

Here's the much simpler Scala version:

def add7(x:Int) = x + 7

While this entire point might sound trivial, its not. Functional style encourages us to write many small methods. Ruby code grows larger than Scala code quickly. The problem also becomes bad when using nested functions. Ruby:

def add7(x)
def add3
x + 3
end
add3 + 4
end

Scala:

def add7(x:Int) = {
def add3 = x + 3
add3 + 4
}


All of this might seem rather trivial, but in practice it definitely adds up.

13 comments:

  1. I don't necessarily think that having the "." is a bad thing though. Both the Scala and Ruby versions read almost exactly the same, and the Ruby version let's you know that "must_be" is a method of the class that x belongs to.

    ReplyDelete
  2. @Darrin

    I totally agree, I don't understand what the author is complaining about.

    ReplyDelete
  3. I think Josh makes a valid point. having to use the "." operator when writing an DSL can be a handicap. How often does one use "." in a sentence? - only at the end of one. Designing a DSL closest to written english would be ideal. While there may be ways to achieve this in Ruby, Scala's syntax seems to support it naturally.

    ReplyDelete
  4. Honestly I've never understood the purpose of trying to write code that look like written english. Code is not english no matter how you make it look.

    IMO the advantage of being able to write "obj method arg" instead of "obj.method arg" is minimal at best.

    ReplyDelete
  5. Ahh the wonderful Ruby attack squad is out in full force; only a few hours after I posted, as well. I wrote this post in 10 minutes on my lunch break, and apparently didn't come up with a convincing example. I will add more later, but for now lets expand just a little bit.

    This code was taken directly from my Equalizer post:

    val x = 5
    x mustBe ( 3 or 4 or 5 )

    Now, try writing this in Ruby...

    x = 5
    x.mustBe ( 3.or(4).or(5) )

    That's a bit of syntactic noise.

    I'll expand tonight, since I'm at work.

    ReplyDelete
  6. For everyone who disagrees (and you're certainly entitled), I'll point you to http://martinfowler.com/bliki/SyntacticNoise.html

    Martin writes:

    "By Syntactic Noise, what people mean is extraneous characters that aren't part of what we really need to say, but are there to satisfy the language definition. Noise characters are bad because they obscure the meaning of our program, forcing us to puzzle out what it's doing."

    And later continues:

    "The noise here, at least for me, is the little things: the ":" to mark a symbol, the "," to separate arguments, the '"' to quote strings."

    You might want to add "." to that last list.

    This is of course, coming from a guy who has done a whole lot of work pushing Ruby.

    Now, I'll point you to another post of his http://martinfowler.com/bliki/BusinessReadableDSL.html

    In this post he talks about business people reading the DSL code (and maybe helping write some of it).

    @Darrin -

    I bring this up to directly refute something you've said - "and the Ruby version let's you know that "must_be" is a method of the class that x belongs to."

    If I am a business person, I have might have no flippin' idea what a method is. I probably don't. Actually, I almost certainly don't.

    While reading the code I'll definitely ask, 'What is this extraneous "."?'

    Don't even try to get me to write anything in this DSL, because I have no idea where the dots are supposed to go, let alone parens, and other noise.

    Imagine a business person trying to read/write this:

    x.mustBe ( 3.or(4).or(5) )

    In most cases, the dot is just absolutely not relevant to the ideas we're trying to convey. The dot is simply an implementation detail.

    @anonymous

    Now you know what I'm complaining about.

    ReplyDelete
  7. Good post and no surprise the fan boys trot out the same stuff. Appreciate your insight and looking forward to the inverse post.

    ReplyDelete
  8. I agree with you on the dot, and think that Scala is generally very good for DSL-s, but Scala has some issues with DSL syntax as well.

    The biggest (to me) is semicolon inference, which sometimes doesn't let you split "sentences" naturally over lines (because it may decide your first line is already a complete expression).

    Another issue is unary methods. For example, given the following:

    class Tea { var sugar=false; var milk=false;
    def withMilk = { milk = true; this }
    def withSugar = { sugar = true; this }
    }
    object Tea {
    def tea = new Tea
    }

    You can't do this:
    import Tea._
    tea withMilk withSugar

    Note: I'm a Scala fan, and certainly prefer it over Ruby.

    ReplyDelete
  9. @villane

    Thanks for the comment.

    As crazy as it may sound, I think that semicolon inference is one of the best things about the language. Trying to write internal dsls, and having to add semicolons is a disaster. If I was writing a Scala>Java post, semicolon inference might come right after closures. Maybe I'm a bit neurotic here, but I'm totally done with the semicolon. Good riddance.

    As to your other point - I do understand how this can be annoying.

    tea withMilk withSugar

    vs

    tea.withMilk.withSugar

    Of course, that's just a tradeoff that has to come when you don't require the dot with method calls.

    The compiler must assume that withSugar is an argument to the withMilk method.

    Or does it? If it finds that withMilk has no arguments, why couldn't it assume that the thing following it is another method call?

    Hmm....Its something to think about anyway.

    Anyway, using traits, you could do something like this:

    object tea extents Tea with Sugar with Milk

    But, I know that wasn't your point.

    ReplyDelete
  10. I like Scala, but a language supposed to use for DSLs that has a reserved keyword "with" is more than annoying.

    ReplyDelete
  11. I see the rubyists' point that the extra dot doesn't hurt readability too much. But let me suggest that the extra dot does impact "write-ability." I concede that code is read more times than it is written. But one attraction of a DSL is that it's writable by non-programmers -- domain experts who can't be asked to grok the subtleties of why the infix plus operator didn't need a dot, but the must_be operator did.

    Also, I think that Jesper asks a very intelligent question. Why try to make code look like a natural language? Well, that's not quite the goal of a DSL, because that really would be too ambitious. One of the real goals of a DSL is to break down communication barriers among humans with heterogeneous skills, not to teach the computer to understand English.

    ReplyDelete
  12. @morgan

    I just reread this and I'm not sure I follow the last part. We aren't teaching the computer to understand English (at least, that is not the goal). What were trying to do is write a library that as natural to read as possible for people who don't know how to read code.

    I know the code isn't English, but if the code is going to be read the often by English speaking people who aren't coders, then I think it makes sense to have the code be as close to their natural language as possible.

    ReplyDelete
  13. W.r.t the apply method, Ruby has a somewhat similar feature in that it lets you define a `[]` method.

    Your array example would translate to this:

    class Array
    def get(index); ...; end
    def [](index); get(index); end
    end

    a = Array.new
    a.get(7)
    a[7]

    In fact, that’s how Ruby’s array subscripts are defined: by way of the `slice` method. See `ri 'Array#[]'` for documentation.

    (I see how old this post is, and that you might know about this already. Still. Note also that I’m not defending Ruby. :-)

    ReplyDelete