Hacker News new | past | comments | ask | show | jobs | submit login

As a Scala programmer I find Ruby syntax complex and inscrutable, almost like Perl.



As a Python programmer, I agree.

Here's a Ruby snippet. I don't want to pick on this particular project [1], rather, I've found that this is typical of Ruby code:

  state_machine :state, initial: :active do
    after_transition any => :blocked do |user, transition|
      # Remove user from all projects and
      user.users_projects.find_each do |membership|
        return false unless membership.destroy
      end
    end
My conclusion? Ruby's syntax is awful. There are colons, absolute value bars, implications, and who-knows-what-else flying everywhere! It gets worse, elsewhere in the same file there are messy lines like this one with single-arrow implications, question marks and ampersands [2] [3]:

  scope :not_in_project, ->(project) \
    { project.users.present? ?       \
    where("id not in (:ids)", ids: project.users.map(&:id) ) : scoped }
Simple, intuitive syntax? From where I sit, the syntax of Ruby is worse than C++, and approaches Perl levels of awfulness. In most languages, I can at least sort-of grok what's going on from the context when I see unfamiliar operators, but not so in Ruby.

This is a copy-paste of a comment I made [4] weeks ago on another article.

[1] https://github.com/gitlabhq/gitlabhq/blob/4caaea824cf51670d1...

[2] https://github.com/gitlabhq/gitlabhq/blob/4caaea824cf51670d1...

[3] I split the line and inserted backslashes because HN gives me a horizontal scrollbar when it's a single long line; apologies if this transformation isn't legal Ruby, or changes the meaning of the code.

[4] https://news.ycombinator.com/item?id=5784296


I'm a professional ruby programmer and your comments opened my eyes. If you don't know what's going on, then that does look like really icky code.

First off, parenthesis in ruby are optional. So calling a method can be done without parenthesis. The following two lines are the same:

    print()
    print
As are these:

    print(1, 2, 3)
    print 1, 2, 3
Here's some bits that might explain better what stuff does:

    :state
If you prefix a word with a colon that creates an object of type Symbol, with value "state", which is a bit like the string "state". The difference is that every mention of :state is a reference to the same object, while if you create a string "state", and a bit further do it again, those are two different objects. We use symbols because it runs faster for conditionals etc: to compare two symbols the interpreter only needs to check the reference, while to compare two strings, every character needs to be checked.

    initial: :active
This creates a Hash with one key-value, with key :initial, and value :active, both symbols. This is equivalent to typing :initial => :active which you encounter a bit further up.

    do |var1, var2| .... end
This creates a block, which is like a closure. The variable names between pipes are the arguments this block takes. Any code inside the block is only run when the block is called. In Ruby, every function can have a block passed by adding do ... end to it. Inside this function, the block can be called using the keyword yield, e.g. yield(1,2) to call the block with arguments 1 and 2. The find_each method will loop through every value of the Array it is called on, and run the block once for every value.

    project.users.present?
The question mark is a valid character for a method name, there's nothing special happening here, present? is just the name of a method. This could easily have been called is_present, or just present.

    a ? b : c
The ternary operator, the same in any language. It translates to "if a, return b, else return c".

    ->(arg1, arg2) { ... }
This creates a lambda, with arguments arg1 and arg2. A lambda is almost the same as a block, so this is almost the same as typing do |arg1, arg2| ... end


This may be the clearest explanation of Ruby syntax I've ever read!

My attempt to parse the above example comes to something like this:

    state_machine(STATE, {"initial" : ACTIVE}, function()
    {
        after_transition({"any" : BLOCKED}, function(user, transition)
        {
            user.users_projects.find_each(function(membership)
            {
                return (membership.destroy ? true : false);
            });
        });
    });
I'm writing in JavaScript because its function notation is way better than Python's lambda syntax. From the parent's explanation, I'm pretty sure this is a valid translation, even though I don't know Ruby. But having three levels of nested anonymous functions really makes the code hard to understand. I'm guessing that this is idiomatic Ruby though, and if you program with the pattern long enough, it gets easier.

OTOH it's still a barrier to entry entirely separate from the syntax -- if the semantics of Ruby code you see "in the wild" is typically this complicated, it's almost as bad as Haskell and its monads! (I have much stronger math chops than most programmers, I've tried to read introductions to monads twice, had them explained to me three separate times on HN, and still don't understand them at all!)


Yep, just about, although if you look closely, the `any` in the original code is not a symbol, but is actually a variable or a method (could be either, we can't know from the sample).

Also, the return in that block would cause an exception, because it doesn't do what the original programmer thinks it does. I guess he never tried out what happens if destroy returns false (which would happen if destroy fails, e.g. because a validation failed).


What you're telling me sounds reasonable, but I thought about it some more, and these rules are so syntactically ambiguous, writing a Ruby parser should be just plain impossible!

If you can call 0-ary functions (functions which take zero arguments) without parentheses, how does Ruby know what a statement as simple as "a = b" does? It must expand internally to something like this (in Python notation):

   a = b() if b.is_function else b
But then if the b() call itself returns a function, then is that function auto-called as well? So we'd have something like this as the Python translation:

   a = b
   while a.is_function:
      a = a()
This can't be right.

And then you get syntactic ambiguities. For example, is the Python translation of the expression x = h-3 (in Ruby) equivalent to (in Python):

(A) x = h-3

(B) x = h(-3)

(C) x = h()-3

(D) x = h()()-3

(E) x = (h-3)()

(F) x = (h()-3)()

Maybe different whitespace, different compile-time definitions, or different run-time values will change the answer! And since the talk says you can monkey-patch Ruby's integer data type, you could even presumably make integers callable so things like "x = (h()-3())()" would be possible!

And then the overloading of the colon and question mark.

Is the expression "k:v" in Ruby a dictionary containing a single key, "k", which is mapped to the value "v"? Or is it a function call of a function called "k" with a single argument, ":v"?

And the ternary operator uses colons too! "a?b:c" could translate as a function called "a?b" being called with a single argument, ":c". Or a dictionary with a key "a?b" which maps to value "c". Or of course the ternary operator! And that's assuming none of the sub-expressions involved are 0-ary functions which are automagically called!

And then what if you want to disable the automagic calling and pass a function object around? Do you have to decorate it with an initial ampersand or something every time you use it? What if you have code like this that puts either a function object or an integer into a variable:

  h = flag ? (&my_function) : 5
Then you want to copy h to the variable y. If you say "y = h" then it does the wrong thing when h is a function, because then h would be auto-called. But if you use the ampersand, you do the wrong thing when h is an integer, because then you would be taking &5 and (in C notation) this would change y from being of type "int" to type "int*". Good grief!

Which all reinforces my original point: Ruby syntax is aggravating! This language is impossible to deal with!

(Sorry for the double reply, but I feel like this comment is different enough from my other reply to merit its own space.)


It's a common joke that Ruby is designed to make it as hard as possible for parsers.

For the code samples going forward, keep in mind that in ruby you don't need a literal return statement to return a value from a method. Just whatever value is last, is returned:

    def val
      5
    end
That method would always return 5.

> But then if the b() call itself returns a function, then is that function auto-called as well?

No, the only way to pass a method is by using method(method_name_here), which returns a Method object. To execute a Method object, you need to call call on it:

     def a
     end
     method(:a).call
So if you have a method that returns a method, it should not be automatically called, so there's no loop necessary:

     def b
       method(:a)
     end

     z = b
Then z is a Method object, namely a.

Some experiments to answer your questions (I admit I wasn't sure for all of them what the outcome would be). IRB is Ruby's repl:

    irb(main):001:0> def a
    irb(main):002:1> 5
    irb(main):003:1> end
    
    irb(main):019:0> {a: 1}
    => {:a=>1}
    irb(main):020:0> {a(): 1}
    SyntaxError: (irb):20: syntax error, unexpected ':', expecting tASSOC



    irb(main):009:0> def h(num = 10)
    irb(main):010:1> num
    irb(main):011:1> end

    irb(main):012:0> h
    => 10
    irb(main):013:0> h-3
    => 7
    irb(main):014:0> h -3
    => -3
    irb(main):015:0> h - 3
    => 7

    irb(main):001:0> a?b:c
    NameError: undefined local variable or method `c' for main:Object


This is really interesting. I guess some of my examples were relying on using a similar lexer as C -- which you probably couldn't in Ruby. I'll have to think some more about what a Ruby lexer would look like...

But again, it steepens the learning curve -- it's so different from other languages that it's actually kind of hard to immediately see all the implications of "well Ruby's lexer is different than C's lexer".


To be honest to understand those needs only the most basic understanding of Ruby. And I suspect you wouldn't be saying that if they'd used C style syntax for blocks, you'd understand it because that syntax is so prevalent in major languages.

I think you're making the mistake that because Ruby isn't a C-style language, the equivalent of a Latin script language, it's not understandable. It's just disadvantaged that you can't immediately make some assumptions about what the operators do based on previous experience.


* __len__() or len() : More than one way to do it ? Oh my !

* Use setuptools, easy_install, pip while we have fun with our rake and gems.

* lambda : Please have fun, with this vomit of a feature.

* __init__.py explain that to a python beginner.

* Have fun with, { "quote": "shit" } while we use our :symbols

* What scope ?

* Have you migrated code to class Foo(object): yet ?

* How is py3 coming along ? Having fun with the worlds cleanest and bossy language community ?

* Write all your list comprehensions line noise, while we add a elegant method.

I just realized, whitespace is also, precisely the entire content of a pythonista's head.


You have a tumor in your brain.




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

Search: