Skip to content

Instantly share code, notes, and snippets.

@lorefnon

lorefnon/ans.md Secret

Last active April 23, 2017 10:18
Show Gist options
  • Select an option

  • Save lorefnon/c707b8aaf50e484256c0e88f95148815 to your computer and use it in GitHub Desktop.

Select an option

Save lorefnon/c707b8aaf50e484256c0e88f95148815 to your computer and use it in GitHub Desktop.

Ruby deviates from most conventional programming languages (esp. C family) in very interesting ways that are very non-intuitive to beginners.

While these choices appear to be outright weird to newcomers, they soon discover that these "gotcha"s are actually interesting innovations in many regards.

Properties with getters and setters:

Lot of programming languages (including javascript, C# etc.) have features like properties with getters and setters which allow you to intercept member assignment (someObject.property = "hello") and member access (const y = someObject.property) as method invocations.

In ruby, method invocation does not require parenthesis. Even when you have no arguments. So some_object.some_property is always an invocation of method some_property on object some_object. In addition some_object.some_property = 10 is always an invocation of method some_property= (yes = is allowed as a part of method name) on object some_object.

While this is quite confusing for people coming from javascript having first class functions where you would expect some_object.some_fn to return you an (unbound) function some_fn rather than executing that function, this has an interesting advanage: Ruby simply does not need a separate concept of properties.

Decorators and Annotations

Languages like python and javascript have special syntax for decorators. Languages like Java and C# have special syntax for annotations which allow you to associate some metadata with a method, class or property.

People coming to Ruby from these langauges are often surprised to find that no analogous features exist in Ruby. This is simply because Ruby's approach of dealing with classes makes them entirely unnecessary.

Ruby classes can contain arbitrary code that gets executed when the class is defined.

class SomeClass
    puts "hello world"
end

The above will print "hello world" when the class is defined (not when instantiated).

The code inside a class can also access the members which have been defined so far. So you can do things like:

class SomeClass
    extend Memoist # Include a third party module which will inject some static member functions
    
    def some_method
        #...
    end
    
    memoize :some_method # Use memoize function injected by Memoist to modify the behavior of some_method defined above
end

Blocks

This is one of the most interesting (and pervasively used) aspect of Ruby. You can pass a block of code to a function.

def some_fn
    some_object.some_other_fn {
        puts "hello world"
    }
end

Languages like Javascript allow you to pass anonymous functions to other functions, and newcomers to ruby from these languages mistakenly assume that blocks are an identical feature. They differ in a significant way - how returns are handled:

def some_fn
   some_object.some_other_fn {
       return 10
   }
end

If some_other_fn chooses to execute the provided block then the return statement returns not only from the block but also from the outer function some_fn. Many people find this totally non-intutive, but this has a powerful consequence - it enables quick refactoring without re-structuring code.

So you can easily refactor out some extraneous "wrapping-logic" like:

def some_fn
    connection = ConnectionPool.borrow_connection
    connection.execute "some statement"
    # ... some other code
    if some_case
        CollectionPool.return_connection connection  
        return
    end
    # ... some other code
    if some_other_case
        CollectionPool.return_connection connection  
        return
    end
    CollectionPool.return_connection connection
    # ... some final logic
end

to something like:

def some_fn
    ConnectionPool.borrow_connection do |connection|
      connection.execute "some statement"
      # ... some other code
      if some_case
          return
      end
      # ... some other code
      if some_other_case
          return
      end
    end
    # ... some final logic
end

In the second implementation, all the return_connection invocations can be abstracted out inside the borrow_connection implementation itself and the consumer does not have to worry about the repetitive return_connection invocations (which can be easily missed out in branches).

Note that apart from that, the outer function (some_fn) does not have to be restructured and even though the returns have gone inside block, the last section ... some final logic will not be executed if a return is encountered.

This is arguably a much cleaner alternative than things like C# IDisposable because disposal of expensive resources can be done independent of garbage collection without requiring any extraneous effort from the developer.

More importantly, someone reviewing the code does not have to wade through all the return_connection invocations, in all edge cases, and focus on the primary logic of the function.


tl;dr Ruby is weird for a reason. Don't let familiarity bias hold you back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment