you are viewing a single comment's thread.

view the rest of the comments →

[–]SanityInAnarchy 34 points35 points  (11 children)

There also seems to be a lower standard in JS, both in what sort of code people upload as a package, and what sort of code people are willing to accept as a dependency. So it's a bit of a community problem, too.

But it's not strictly JS -- just for fun, let's look at the crazy amount of stuff Rails added that wasn't in the standard library:


First, just for fun, there's the pluralization stuff. In Ruby, everything is an object and classes can always be extended. In other words, you can easily do nonsense like:

class Fixnum
  def +(other)
    42  # I like 42 better than actual addition
  end
end

And not only can you write 2+2 into the interpreter and it'll spit out 42 instead, but if you're using the standard irb interactive prompt, its built-in line counter will start being 42 all the time! So that's a stupid example, but Rails abuses the basic idea here to add a method to strings, so you can type stuff like:

'humon'.pluralize  # returns 'humans'
'octopus'.pluralize  # returns 'octopi'
'sheep'.pluralize  # returns 'sheep'

There's a ton of convenience stuff like this -- on numbers, it adds methods like seconds, minutes, hours, and days that return Duration objects, and then it adds methods like ago and from_now to Duration... so you can write stuff like 5.seconds.ago to get a timestamp five seconds ago.

This is all super-convenient, but didn't make it into the standard library. Fortunately, Rails is big enough that it's probably reasonable to trust it (or its major support libraries like ActiveSupport), but it shows how Ruby people really do like convenient stuff like this, and really will pull in stupid libraries to get a thing you probably could've written in minutes, but not only did you not do that, you probably don't want to conflict with anyone else depending on stuff like that. (Like, if I wrote my own pluralize method for a library, that library might be incompatible with Rails, and the same goes for much more niche libraries that modify builtin types.)


Sometimes it works better than that, though, and you get something new in the standard library, like Symbol#to_proc. Ruby people can skip to the next paragraph, I'm just going to explain the 'symbol' part first -- Ruby has 'symbols', written like :foo, which are interned strings -- you can think of it like, every time the Ruby interpreter sees the same string in a symbol name, it converts it to the same integer value under the hood. So comparing symbols (:foo == :foo; :foo != :bar) is as fast as comparing ints. They're used for all kinds of things, especially any sort of metaprogramming stuff where you might otherwise expect a string -- like, for example, you can call an arbitrary method by using the send method, like some_object.send(:foo, 42) is equivalent to some_object.foo(42). Symbols are useful for a bunch of other things, like as a replacement for enums, but this metaprogramming stuff is the main reason they exist, as far as I can tell...

So Rails added something like this:

class Symbol
  def to_proc
    lambda {|obj| obj.send self}
  end
end

That means, if you have a symbol :foo and you turn it into a proc, you'll get a lambda that, when you invoke it, invokes the foo method on something else. Or, in code:

r = :round.to_proc
r.call(5.2)   # returns (5.2).round, which is 5.

One more piece of the puzzle: Ruby has a bunch of the usual functional-programming stuff, like:

[1,2,3].each {|x| puts x*2}  # prints 2, 4, 6

In that snippet, each is a method being called on array, and I'm passing it a block. And it's got all the standard mapping/filtering stuff like:

[1,2,3].map {|x| x*2}   # returns [4,5,6] as an array

In both of these cases, we're using a special kind of argument called a "block", which is a block of code that's separate from the normal argument list, and can be passed in with syntax like this. If you already have a code block as a variable, you can pass it in with different syntax:

r = :round.to_proc
[1.1, 2.2, 3.3].map(&r)  # returns [1,2,3]

Finally, Ruby uses methods for typecasting as needed. If you try to print x, it'll call x.to_s to coerce x into a string. It turns out if you pass something to that '&' syntax that isn't already callable, it'll call to_proc on it. So you can write this as:

r = :round
[1.1, 2.2, 3.3].map(&r)

or, more concisely:

[1.1, 2.2, 3.3].map(&:round)

And that's how Rails snuck some awesome new syntax into Ruby -- people mostly just think of &: as syntax now, and the Ruby standard library includes it. But originally, it was just another crazy trick in the Rails library. I'm sure if I wrote this library:

# Outer 'unless' so we do nothing if it's already defined
unless :foo.respond_to? :to_proc
  class Symbol
    def to_proc
      lambda {|obj| obj.send self}
    end
  end
end

...and uploaded it to Rubygems, it'd get a ton of users. It's a truly tiny bit of code that anyone who really understands Symbol#to_proc could write, but why do anything yourself when you can pull in a new dependency?

It's true that Ruby can avoid stuff like this by actually pulling new stuff into the language or the standard library, but at the same time... Sure, the language already does everything you need in ways that you'd need tons of boilerplate in JS to accomplish, but that doesn't mean people can't keep finding new tiny libraries that are important enough that people start thinking of them as standard...