However blocks are special forms of the language, unless reified to procs they can only be passed as parameter (not returned), and a method can only take one block. They also have some oddities in how they interact with parameters (unless reified to lambda procs).
[1] because Ruby has something called "lambda procs"
let isLarge = a => a>100;
numbers.filter(isLarge)
Blocks let you do the same but without extracting the body as cleanly. Maybe it’s a chronological issue, where Ruby was born at a time when the above wasn’t commonplace?
>When you write 5.times { puts “Hello” }, you don’t think “I’m calling the times method and passing it a block.” You think “I’m doing something 5 times.”
I’m of two minds about this.
On the one hand, I do agree that aesthetically Ruby looks very clean and pleasing. On the other, I always feel like the mental model I have about a language is usually “dirtied” to improve syntax.
The value 5 having a method, and that method being an iterator for its value, is kinda weird in any design sense and doesn’t seem to fix any architectural order you might expect, it’s just there because the “hack” results in pretty text when used.
These magical tricks are everywhere in the language with missing_method and the like, and I guess there’s a divide between programmers’ minds when some go “oh that’s nice” and don’t care how the magic is done, and others are naturally irked by the “clever twists”.
I don't think this is particularly weird, in Ruby at least. The language follows object orientation to its natural conclusion, which is that everything is an object, always. There is no such thing as "just data" in Ruby, because everything is an object. Even things that would just be an `int` in most other languages are actually objects, and so they have methods. The `times` method exists on the Integer classes because doing something exactly an integer number of times happens a lot in practice.
The issue is more with this part:
>The `times` method exists on the Integer classes because doing something exactly an integer number of times happens a lot in practice.
It is practical, but it breaks the conceptual model in that it is a hard sell that “times” is a property over the “5” object.
The result is cleaner syntax, I know, but there is something in these choices that still feels continually “hacky/irky” to me.
This adds some complexity in the language, but it means that it’s far more expressive. In Ruby you can with nothing but Array#each write idiomatic code which reads very similar to other traditional languages with loops and statements.
let isLarge = a => a>100;
as a lambda and call via #call or the shorthand syntax .(): is_large = ->(a) { a > 100 }
is_large.call(1000)
# => true
is_large.(1000)
# => true
I find the .() syntax a bit odd, so I prefer #call, but that's a personal choice. Either way, it mixes-and-matches nicely with any class that has a #call method, and so it allows nice polymorphic mixtures of lambdas and of objects/instances that have a method named 'call'. Also very useful for injecting behavior (and mocking behavior in tests).Additionally, you can even take a reference to a method off of an object, and pass them around as though they are a callable lambda/block:
class Foo
def bar = 'baz'
end
foo_instance = Foo.new
callable_bar = foo_instance.method(:bar)
callable_bar.call
# => 'baz'
This ability to pull a method off is useful because any method which receives block can also take a "method object" and be passed to any block-receiving method via the "block operator" of '&' (example here is passing an object's method to Array#map as a block): class UpcaseCertainLetters
def initialize(letters_to_upcase)
@letters_to_upcase = letters_to_upcase
end
def format(str)
str.chars.map do |char|
@letters_to_upcase.include?(char) ? char.upcase : char
end.join
end
end
upcase_vowels = UpcaseCertainLetters.new("aeiuo").method(:format)
['foo', 'bar', 'baz'].map(&upcase_vowels)
# => ['fOO', 'bAr', 'bAz']
This '&' operator is the same as the one that lets you call instance methods by converting a symbol of a method name into a block for an instance method on an object: (0..10).map(&:even?)
# => [true, false, true, false, true, false, true, false, true, false, true]
And doing similar, but with a lambda: is_div_five = ->(num) { num % 5 == 0 }
(0..10).map(&is_div_five)
# => [true, false, false, false, false, true, false, false, false, false, true]
In addition they have nonlocal return semantics, somewhat like a simple continuation, making them ideal for inline iteration and folding, which is how most new Rubyists first encounter them, but also occasionally a source of surprise and confusion, most notably if one mistakenly conflates return with result. Ruby does separately have callcc for more precise control over stack unwinding, although it’s a little known feature.
That said, we don't need just one programming language. Perhaps Ruby is easier to learn for those new to programming and we should introduce it to students.
stonecharioteer•3d ago
pjmlp•1h ago
frou_dh•37m ago
stonecharioteer•13m ago
janfoeh•1h ago
I still love this language to bits, and it was fun to relive that moment vicariously through someone elses eyes. Thanks for writing it up!
stonecharioteer•12m ago
PufPufPuf•1h ago
pjmlp•4m ago