[HN Gopher] Ruby Blocks
___________________________________________________________________
Ruby Blocks
Author : stonecharioteer
Score : 168 points
Date : 2025-10-14 15:58 UTC (4 days ago)
(HTM) web link (tech.stonecharioteer.com)
(TXT) w3m dump (tech.stonecharioteer.com)
| stonecharioteer wrote:
| I've been very taken by Ruby and how it uses blocks everywhere!
| This is an article I wrote just to emphasize that.
| pjmlp wrote:
| Have a look at Smalltalk blocks, or FP languages, to see where
| Ruby's inspiration comes from.
| frou_dh wrote:
| An interesting thing about Smalltalk and Ruby blocks is that
| they aren't just anonymous functions/lambdas, right? i.e. if
| you 'return' / '^' in a block, it's the context around the
| block that you return from, not just the block itself? That's
| what struck me about both of them when I was used to thinking
| in basic Lisp terms.
| pjmlp wrote:
| Yes, that is why that behaviour is known as closures.
|
| Also why in languages like C++, you get to control what is
| captured from the calling context.
| vidarh wrote:
| As much as I love Ruby (and I love it a lot - I right now
| have Claude fix eigenclass compilation issues in another
| window in my long-languishing Ruby compiler hobby project),
| this is also the root of a lot of confusion.
|
| Ruby has two types of closures: lambda's, where return
| returns to the caller, and proc's/blocks where return acts
| as a return in the defining scope.
|
| Beginners often struggle with the distinction between
| lambda and block, and often will be wildly confused if they
| ever see "proc" in the wild.
|
| Yet lambda and proc both return a Proc object, and if you
| take the value of a block, you also get a Proc object...
| Just with different return semantics...
|
| It'd be nice if this was a bit clearer.
| stonecharioteer wrote:
| My next post, which is on loops, is about the common stuff
| with smalltalk as well!
| vidarh wrote:
| Ruby is basically what happens when Smalltalk and Perl
| (compare the _command line_ and the "$"-variables for
| example) has a baby and it inexplicably looks much better
| than both parents.
| janfoeh wrote:
| I discovered Ruby (through Rails) about twenty years ago on the
| dot. Coming from Perl and PHP it took me a while, but I
| remember the moment when I had the same realisation you did.
|
| 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 wrote:
| I'm glad folks are having fun reading this. I want to write a
| few more articles, particularly dissecting the internals of
| Ruby and how amazing it feels.
| PufPufPuf wrote:
| Take a look at Kotlin, it perfected this idea
| pjmlp wrote:
| What Kotlin offers is already present in Scala or other
| languages from ML linage.
| hshdhdhehd wrote:
| Is a block basically a lambda or is there more to it?
| simonask wrote:
| They are closures. But Ruby can do interesting and slightly
| reckless things, like transplanting a closure into a different
| evaluation scope. It's very powerful, and also very dangerous
| in the wrong hands.
| hshdhdhehd wrote:
| Sounds a bit like a lisp macro? Or in JS using eval?
| somewhereoutth wrote:
| My understanding is that the 'extra thing' is control flow -
| blocks can force a return in their calling scope. For example a
| loop that calls a block may be terminated/skipped by a
| break/continue statement in the block itself. However I'm not a
| Ruby programmer, so please check my working.
| masklinn wrote:
| Assuming that by lambda you mean "an anonymous function"[1],
| for most intents and purposes they are, except their returns
| are non-local whereas functions usually have local returns.
|
| 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"
| kace91 wrote:
| Coming from a language with functions as first class objects,
| blocks felt a bit limited to me, because it feels as if you
| almost have functions but not really, and they get inputted by a
| back door. Used for example to:
|
| 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".
| WJW wrote:
| > 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.
|
| 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.
| kace91 wrote:
| I don't have an issue with the "everything's an object" part,
| because it _is_ consistent, even though it gets a bit trippy
| when classes are objects as well and they are implementation
| of a Class class which is an implementation of itself
| (trickery again!).
|
| 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.
| WJW wrote:
| Perhaps I've been doing Ruby for too long, but it's still
| not that weird to me. The quantity "5" is very abstract
| without anything to have "5" of. That is why "5.days" and
| "5.times" exist, among others. Mathematically it makes just
| as much sense to start with the amount and add the unit
| later than it does to start with the unit and add the
| amount later (ie like `time_to_wait = SECONDS_IN_A_DAY * 5`
| as you might do in some other languages).
| kace91 wrote:
| Maybe it is clearer if I explain it in syntactic terms?
| In my mental model objects are nouns (described entities)
| and methods are verbs - actions over the noun.
|
| process.start() is the action of starting done by the the
| noun that is the process.
|
| It's not exactly a matter of naming, as some methods are
| not explicitly verbs, but there is almost always an
| implicit action there: word.to_string() clearly has the
| convert/transform implication, even if ommitted for
| brevity.
|
| I see no path where 5 is a noun and times the verb, nor
| any verb I can put there that makes it make sense. If you
| try to stick a verb (iterate?) it becomes clear that 5 is
| not the noun, the thing performing the iteration, but a
| complement - X iterates (5 times). Perhaps the block
| itself having a times object with 5 as an input would
| make it more standard to me (?).
|
| But I do understand that if something is extremely
| practical a purist/conceptual argument doesn't go very
| far.
| oezi wrote:
| I have been doing Ruby for so long that it feels very
| natural to apply a method in this way on the instance.
|
| false.not
|
| applies the not method on the false instance in the same
| way that
|
| car.start
|
| in every OO language calls the start method on car as the
| receiver.
|
| So filter(list) feels just wrong when you are clearly
| filtering the list itself.
| nasmorn wrote:
| Although I prefer Elixir currently I agree that ruby at
| least goes all the way in on OO and not having to
| remember which feature is implemented as a language
| syntax and what is just a method invocation is a strength
| not a weakness. It is different in other languages for
| historical performance reasons really.
| kace91 wrote:
| list.filter is ok! Filtering is an action that applies to
| a list
|
| false.not is borderline but if read as false.negate it
| makes sense (negating is an action that applies to a
| Boolean value). That wording screws the chaining though.
|
| 5.times is where the pattern breaks: times is not an
| action that applies to a number (nor an action at all).
| It's the block the one that should repeat/iterate - but
| Ruby breaks the rule there and blocks are not an object
| (!). If they were you could block.repeat(5) which IMO is
| cleaner.
| chao- wrote:
| There is a bit of personal preference in what "applies to
| a number", but I see what you mean.
|
| As a slight correction, a block is indeed an object! They
| are received by methods as an instance of the Proc class:
| def inspects_block(&block) puts block
| puts block.class end inspects_block { "foo" }
| # => #<Proc:0x0000000000000000> # => Proc
|
| You can even add a 'repeat' method to these in the way
| that you specified, although you will need to add '->' to
| declare the block (as a lambda, which is also just an
| instance of Proc) before you call #repeat on it:
| class Proc def repeat(n) n.times {
| self.call } end end ->{ puts("foo")
| }.repeat(3) # => foo # => foo # => foo
| kaiuhl wrote:
| Blocks are actually instances of the Proc class. There
| are helper methods to handle blocks passed to methods in
| a lightweight manner but you can also accept the block as
| a method argument, e.g., class Integer
| def times(&blk) i = 0 while i < self
| blk.call(i) i += 1 end end
| end
| kevinmchugh wrote:
| Fun fact - there's no Boolean class in Ruby. True is an
| instance of TrueClass and false is an instance of
| FalseClass
| isr wrote:
| Hmm, but that's not really a "breakage" in ruby, it's
| more an aesthetic argument over which objects should have
| which logic. It's like naming things. Smalltalk (& ruby)
| has 'select'. Everyone else uses 'filter'.
|
| To some, 5.times seems very readable & logical. It's like
| arguing over the "right" colour scheme to use while
| coding (BTW, the correct answer is solarised light, but
| with black foreground text!!)
| bigtunacan wrote:
| It's not just about practicality. Ruby is using message
| passing, not method calling. This is fundamentally
| different and a bit foreign to the larger community. Then
| ruby layers syntactic sugar on top that hides this.
|
| Behind the scenes everything is a message passed using
| __send__ and you can do this directly as well, but you
| generally don't.
|
| So when you write
|
| 5.times { puts "Hello" }
|
| It's sort of expected by the average programmer that you
| are telling 5 to call the times method and expect it to
| exist and do what it's told.
|
| In reality you have indirectly sent a message that looks
| like
|
| 5.__send__(:times) { puts "Hello" }
|
| What we are really doing is sending a message to 5 (the
| receiver) and giving it the opportunity to decide how to
| respond. This is where method_missing comes in to allow
| responding in a custom fashion regardless if a method was
| explicitly defined.
|
| So you're not telling 5 to call the method times, rather
| you are asking, "Hey 5, do you know how to handle the
| message times?"
|
| These are fundamentally different things. This is
| actually super important and honestly hard to really grok
| _especially_ in ruby because of the syntactic sugar. I
| came from a C/C++ background originally, then Java and
| then moved to Ruby. After a few years I thought I
| understood this difference, but honestly it wasn't until
| I spent a couple years using Objective-C where message
| passing is happening much more explicitly that I was able
| to truly understand the difference in a way that it
| became intuitive.
| anamexis wrote:
| I'm a rubyist, but _how_ is message passing fundamentally
| different from method calling? I get that method_missing
| adds a twist, but your comment doesn't explain what the
| fundamental difference _is_.
|
| Especially in the context of Fixnum#times. How does
| message passing vs method calling matter there? #times is
| just a method on Fixnum.
| isr wrote:
| Because it leaves it up to the object being called what
| to actually do with the message. The object you're
| talking to might be forwarding its messages to another
| object in another ruby instance on another machine (if
| the current machine is getting fully loaded, etc), and
| the caller would be none the wiser. And crucially, the
| caller wouldn't have to be modified to enable this. The
| logic for this would be entirely within the object being
| called.
|
| So the difference isn't just with method_missing.
|
| With "method calling" as you put it, the program blows up
| if that object doesn't have that method, WHEN YOU CALL
| IT.
|
| Basically, this smalltalk oo paradigm is about shifting
| where you put more of your logic. At & around the "call"
| site, or within the object whom you're calling &
| entrusting to do something useful with it.
|
| All hearkening back to Alan Kay's original ideas about
| biology influencing how we organise code, and having a
| program be 1000's of "little black boxes" where work gets
| done by all these boxes talking to each other.
|
| Which is why smalltalk (& ruby implements the Smalltalk
| object model to its entirety) actually has an awful lot
| in common with Erlang & other BEAM runtimes, even though
| those are functional languages. Because once you get
| beyond the techy buzzwords, the main ethos behind them is
| actually quite similar.
| anamexis wrote:
| I guess what I'm getting at, is that I don't understand
| how difference actually informs anything concretely, as
| in the example of Fixnum#times, where this discussion
| started. Why is it super important to understand this
| fundamental difference?
| gray_-_wolf wrote:
| > that "times" is a _property_ over the "5" object
|
| Maybe here is the confusion, ruby is based on message
| passing, so the `times` is a message you are sending to 5,
| not a property of it.
| bigtunacan wrote:
| I think you're right, but I also suspect that doesn't
| clear up anything for most people as in my experience
| they generally don't grok the difference unless they've
| already spent a significant amount of time in something
| like smalltalk or Objective-C
| majormajor wrote:
| When you're new it just looks like a weird-but-reasonable
| syntax.
|
| When you learn the language you really fall into two camps:
|
| - ah, yes, this is unusual, but it's consistent and now i
| understand the language
|
| - this is way too clever
|
| I'm more in the first camp.
| cortesoft wrote:
| Times isn't a property of the 5 object, it is a method of
| the 5 object.
| TOGoS wrote:
| `5.times` is not so outlandish, though it would seem better
| for that to be in a `Loop` library or something
| (`Loop.times(5) { do some stuff }`).
|
| The `5.days` example that was posted somewhere else in this
| thread might be a better example. It is not, as far as I can
| tell, part of Ruby's core library, thank goodness, but it is
| the kind of thing the methods-as-hacks-for-syntax culture
| seems to encourage. My question being "why the heck should
| the number 5 know anything about days? What does 5.days even
| mean, given that there are various ways to interpret 'a
| day'?"
|
| This kind of bad design has made its way all over the place.
| The Java mocking libraries that my coworkers like to use are
| full of it. Long chains of method calls that appear like
| they're trying to 'look like English' but make no damn sense,
| so you have to dig into each method call and what kind of
| thing it returns to understand what this chain actually
| means.
| layer8 wrote:
| With similar justification, you could argue that blocks
| should have a method `times` taking an integer, because
| repeating a block an integer number of times happens a lot in
| practice. I'd even argue that it is conceptually closer to
| blocks than to integers, and therefore the method belongs on
| the block. Hence you'd write `{ puts "Hello" }.times 5`.
|
| But blocks are not objects in Ruby, so you can't do that, and
| everything actually _isn't_ an object in Ruby.
|
| Also, it's even more common to do something depending on
| whether a condition is true or false, but true and false in
| Ruby don't have a method to (not) execute a block on them,
| and you use a non-OOP `if` instead, so what's up with that?
| bashkiddie wrote:
| > everything is an object, always.
|
| I beg to differ. What object does the method `puts` belong
| to? Why do you not call puts with its objects name?
|
| Ruby has a concept of mixins (Golang interfaces), these are
| not objects. Neither is `puts`
| kazinator wrote:
| Answerable by a few minutes of googling. Sort of:
|
| puts is a method which has a class: the Method class:
| irb(main):001:0> method(:puts) => #<Method:
| main.puts> irb(main):002:0> method(:puts).class
| => Method
|
| Everything being a confused muddle in Ruby, there is
| evidently some Kernel base class that is injected into
| every Object, and puts is a private method in that:
| irb(main):003:0> 3.puts() Traceback (most recent call
| last): 2: from /usr/bin/irb:11:in `<main>'
| 1: from (irb):3 NoMethodError (private method `puts'
| called for 3:Integer)
|
| The Method class of puts is a real class with methods and
| all: irb(main):004:0>
| method(:puts).class.methods => [:allocate,
| :superclass, :<=>, :<=, :>=, :==, :===, :autoload?,
| :autoload, :included_modules, :include?, :name, :ancestors,
| :attr, :attr_reader, :attr_writer, :attr_accessor,
| :instance_methods, :public_instance_methods,
| :protected_instance_methods, :private_instance_methods,
| :constants, :const_get, :const_set, :const_defined?,
| :class_variables, :remove_class_variable,
| :class_variable_get, :class_variable_set,
| :class_variable_defined?, :public_constant, :freeze,
| :inspect, :deprecate_constant, :private_constant,
| :const_missing, :singleton_class?, :prepend, :class_exec,
| :module_eval, :class_eval, :include, :<, :>,
| :remove_method, :undef_method, :alias_method,
| :protected_method_defined?, :module_exec, :method_defined?,
| :public_method_defined?, :to_s, :public_class_method,
| :public_instance_method, :define_method,
| :private_method_defined?, :private_class_method,
| :instance_method, :instance_variable_set,
| :instance_variable_defined?, :remove_instance_variable,
| :instance_of?, :kind_of?, :is_a?, :tap,
| :instance_variable_get, :instance_variables, :method,
| :public_method, :singleton_method,
| :define_singleton_method, :public_send, :extend, :to_enum,
| :enum_for, :pp, :=~, :!~, :eql?, :respond_to?, :object_id,
| :send, :display, :nil?, :hash, :class, :singleton_class,
| :clone, :dup, :itself, :yield_self, :taint, :tainted?,
| :untrust, :untaint, :trust, :untrusted?, :methods,
| :frozen?, :protected_methods, :singleton_methods,
| :public_methods, :private_methods, :!, :equal?,
| :instance_eval, :instance_exec, :!=, :__send__, :__id__]
| cortesoft wrote:
| > Everything being a confused muddle in Ruby
|
| It really isn't a confused muddle, the rules are very
| clear. Just because it doesn't match what you expect from
| your other language experience doesn't mean it isn't
| clear.
| pxc wrote:
| I'm not a Rubyist really, but I started using it at work
| a few weeks ago for a very small script. I'm not allergic
| to RTFM, so I picked up the canonical reference book on
| Ruby and occasionally visit the official docs for the
| language. I agree: the structure is clear and
| straightforward and there's nothing difficult about
| learning it.
|
| It's also the opposite of magic; magic is when language
| features can't be described in terms of the language
| itself.
| WJW wrote:
| It's not as much a mystery as you think, the Kernel
| module is pretty well documented and states right in the
| first paragraph that its methods are available in all
| Ruby objects: https://ruby-doc.org/core-3.0.2/Kernel.html
| chao- wrote:
| _> Ruby has a concept of mixins (Golang interfaces), these
| are not objects._
|
| Ruby "mixins" are the affordance of sharing instance
| methods from a module via a keyword. Modules are objects,
| and are instances of class Module: module
| ProvidesFooMixin def foo = "foo" end
| class Bar include ProvidesFooMixin end
| Bar.new.foo # => "foo"
| puts(ProvidesFooMixin.class) # => Module
| ProvidesFooMixin.object_id # => (some integer value)
|
| _> Neither is `puts`_
|
| Like all methods, `puts` is an object:
| method(:puts) # => #<Method: Object(Kernel)#puts(*)>
| method(:puts).class # => Method
| method(:puts).object_id # => (some integer value)
|
| Here you see evidence of where `puts` comes from:
| Kernel#puts via Object, which I will now explain in detail.
|
| _> What object does the method `puts` belong to?_
|
| It belongs to the object you are calling it from within.
| You don't need to call `puts` with a receiver because it is
| an instance method, just like you don't need to call an
| instance method `foo` via `self.foo`. But you _could
| choose_ to use a receiver, since the `puts` you know and
| love is just another instance method. You can try
| `self.puts` for yourself in some context!
|
| Your classes (and their instances) inherit this `self.puts`
| instance method from the Object class, which includes the
| Kernel module, which provides `Kernel#puts`. So the only
| reason you can send it as a message without a receiver is
| because it is just another instance method (again, the same
| as calling instance method `#foo` without using
| `self.foo`).
|
| Caveat: You can build an "alternate universe" object
| hierarchy by inheriting from BasicObject, and in your
| alternate universe, you can choose to not `include Kernel`,
| and you will see that instances of your new objects do not
| have access to `puts` in their instance scope.
| jfabre wrote:
| Modules (mixins) are objects, classes are also objects (of
| type Class).
|
| The Kernel module is included in the Object class, which
| means its methods are available to every Ruby object and
| can be accessed from virtually any scope, including the
| top-level (global) context where self is an instance of
| Object called "main."
| cortesoft wrote:
| Well, "puts" is a method defined in the Kernel module,
| which is included in the Object class, which means it is
| available in all contexts (since every object is a subclass
| of the 'Object' class). So, to answer your question, the
| puts method belongs to the Kernel module object, and the
| Kernel module object is an instance of a Module object.
| WJW wrote:
| `puts` is just a method of the Kernel module: https://ruby-
| doc.org/3.4.1/Kernel.html#method-i-puts, just like `p` and
| many others. Kernel is included in the Object class that is
| the root of the class hierarchy, so its methods are
| available in every ruby object.
|
| Mixins are just modules, which are objects, which you can
| call methods on. (Or rather, send messages to) You can
| easily verify this in irb calling a method on (for example)
| the Enumerable module: irb(main):001>
| Enumerable.class => Module
|
| You are right that a module is not a class, and it is not
| possible to call `.new` on it. But the module itself is
| very much an object.
| pxc wrote:
| > I beg to differ. [...] Why do you not call puts with its
| objects name?
|
| In the first place, I'd say what you're asking for goes
| beyond "everything is an object".
|
| But I think your questions can be answered in a way that
| affirms that "everything is an object" in Ruby anyway.
|
| > Why do you not call puts with its objects name?
|
| Because it belongs to whatever object you're working in
| already; `puts` is identical to `self.puts`. And yes,
| you're always working in an object:
| https://bparanj.gitbooks.io/ruby-
| basics/content/chapter1.htm...
|
| > What object does the method `puts` belong to?
|
| As indicated above, it belongs to the object `self`. It
| gets added object via the mixing-in Kernel module into
| Object. Kernel is itself an instance of class Module:
| https://docs.ruby-lang.org/en/3.4/Module.html
|
| The `puts` in Kernel delegates to `puts` from IO, which is
| likewise an instance method belonging to the object you can
| refer to by the name `$stdout`: https://docs.ruby-
| lang.org/en/3.4/IO.html
|
| > Ruby has a concept of mixins [and], these are not
| objects.
|
| Sure they are. Mixins themselves inherit from Module, and
| Modules are also objects (just like classes are).
|
| Some highlights from Chapter 27 ("Library Reference: The
| Class Model") of the recent edition of the pickaxe book
| (emphasis mine):
|
| > The Kernel module is included by class Object, so its
| methods are available in every Ruby object. One of the
| reasons for the Kernel module is to allow methods like
| `puts` and `gets` to be available everywhere and even to
| _look like_ global commands. Kernel methods allow Ruby to
| _still maintain an "everything is an object semantics"._
|
| and regarding mixins:
|
| > The Module class is the class of any module you declare
| with the `module` keyword. Each module is an instance of
| class Module.
|
| on Object:
|
| > Object is the parent class of (almost) all classes in
| Ruby unless a class explicitly inherits from. BasicObject.
| [...] Object _mixes in_ the _Kernel module_ , making the
| built-in functions globally accessible.
|
| tl;dr: mixins in Ruby are instances of class Module, and
| their methods end up bound to instances of class Object.
| Abstract module methods that don't belong to a concrete
| instance of some class that mixed in their module belong to
| the Object that is the module itself (the instance of class
| Module). (The same kind of thing is how class methods
| work.)
| judofyr wrote:
| Blocks are fundamentally different from functions due to the
| control flow: `return` inside a block will return the _outer_
| method, not the block. `break` stops the whole method that was
| invoked.
|
| 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.
| oezi wrote:
| You are right on return (use next in a block), but break uses
| block scope.
| judofyr wrote:
| Maybe I explained it a bit imprecise. I was trying to
| explain the following behavior: def foo
| p 1 yield p 2 end
| foo { break }
|
| This only prints "1" because the break stops the execution
| of the invoked method (foo).
| Mystery-Machine wrote:
| WAT? I'm a 12+ years Ruby developer and I didn't know
| this.
| vidarh wrote:
| More specifically blocks (and "proc"'s) return _from the
| defining scope_. This is just a minor clarification, but it
| matters, because if you pass a block _down_ from where it is
| defined, and the block calls "return" it will still not just
| exit from the method where it was called, but from the method
| where it was _defined_.
|
| This can sometimes be useful: A calling method can pass down
| a block or proc to control if/when it wants an early return.
|
| Basically Ruby has two types of closures:
|
| * A return in a lambda returns _to the calling scope_. So
| basically, it returns to after where the "call" method is
| invoked.
|
| * A return in a block or a proc returns from the _scope in
| which it was defined_ (this is also why you get
| LocalJumpError if you try to return a block or a proc, but
| not a lambda to the method _calling_ the one where the block
| or proc is defined).
|
| When you name a block, you get a Proc object, same as you get
| when you take the value of a lambda or proc.
|
| In practice, that blocks in MRI are not Proc objects already
| is just an implementation detail/optimisation. I have a long-
| standing hobby project to write a Ruby compiler, and there a
| "proc" and a bare block are implemented identically in the
| backend.
| jhbadger wrote:
| If you are familiar with a true object-oriented language like
| Smalltalk (rather than the watered-down form of OO in C++,
| Java, etc.), an integer like 5 having methods makes sense
| because it (like everything else) is an object. Objects in Ruby
| aren't just window dressing -- they are its core.
| ck45 wrote:
| But then Ruby only goes half way, not unlike the "watered-
| down form" in your term. Why is `#times` a method of Integer,
| but `#if` (or `#ifTrue`) not a method of booleans like in
| Smalltalk? Ruby does the same cherry picking from Smalltalk
| like everybody else, just different cherries. When looking at
| Ruby, it feels like the simple examples are all nice and
| clean but then the weird details start to appear and the
| language feels more hacky than others (like Ned Flander's
| house in Simpsons S08E08).
| chao- wrote:
| #if and #ifTrue are yours if you want them:
| class TrueClass def if = true def ifTrue =
| true end class FalseClass def if =
| false def ifTrue = false end
| true.if # => true false.if # => false
| wild_egg wrote:
| In Smalltalk those methods don't return `true`. They take
| a block and evaluate it if the boolean receiving the
| message (a > b) ifTrue: [ "do
| something" ]
|
| EDIT: to clarify what's happening there, `>` is a message
| sent to `a` that will result in a boolean. The True class
| and False class both understand the ifTrue: message and
| `True>>ifTrue:` executes the block whereas
| `False>>ifTrue:` just throws it away.
|
| There's no `if` keyword in the language. Control flow is
| done purely through polymorphism.
| chao- wrote:
| I apologize for my lack of Smalltalk knowledge. As you
| can imagine, you can do similar in Ruby by defining
| ifTrue to accept a block, even adding ifTrue on other all
| objects and defining something similar:
| class TrueClass def ifTrue(&block) = block.call
| end class FalseClass def
| ifTrue(&block) = nil end class Object
| def ifTrue(&block) = block.call end
| class NilClass def ifTrue(&block) = nil end
|
| If ck45's core complaint was that this is not baked into
| the language, I will agree that it is less convenient for
| lack of a default.
| oezi wrote:
| Certainly possible: add ifTrue as a method to TrueClass
| and FalseClass.
|
| It just isn't very fast.
| codesnik wrote:
| problem is not with ifTrue, and not with it's
| performance, it's easy to do. it is "ifTrue:ifFalse:"
|
| also it is common to do assignments in the "if", and with
| actual method and blocks scope of the introduced variable
| would be different and everyone would be tripping on it
| all the time.
| codesnik wrote:
| basically it's because of "else" and "elsif". While ".each"
| works the same as "for .. in ...; end", it's harder to do
| "if else" as method which will also return value of the
| block inside the branch. Smalltalk can do it because
| "ifTrue:ifFalse:" is _one_ message, ruby didn't go that way
| syntactically.
| chao- wrote:
| The "aesthetically pleasing" aspect of blocks is not mutually
| exclusive with real, first-class functions! Ruby is really more
| functional than that. Ruby has both lambas and method objects
| (pulled from instances). For example, you can write:
| 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]
| kace91 wrote:
| That is interesting! I haven't explored Procs much, since I
| use ruby for a shared codebase at work and I was originally a
| bit afraid of trying to push unidiomatic ideas in the
| codebase.
|
| In your experience, is it ok to use Procs for example for
| extraction of block methods for cleanliness in refactors? or
| would I hit any major roadblocks if I treated them too much
| like first-class functions?
|
| Also, is there any particular Rails convention to place
| collections of useful procs? Or does that go a bit against
| the general model?
| vinceguidry wrote:
| You shouldn't have much difficulty, Ruby converts blocks to
| Procs whenever it needs an actual object. Their semantics
| are intentionally kept the same. This is unlike lambdas,
| whose semantics are closer to methods.
|
| Pass the wrong number of of arguments to a Proc or block,
| it will pass nil for missing args and omit extras. Pass the
| wrong number of arguments for a method or lambda and you
| get an ArgumentError. Use the return keyword in a lambda,
| it returns from the lambda, just like if you call return in
| a method. In a block or Proc, it returns from the calling
| method.
|
| So I would feel comfortable leaning on them for refactoring
| as it's as Ruby intended. Just use lambdas when you want to
| turn methods into objects and Procs when you want to
| objectify blocks.
|
| You should get ahold of a copy of Metaprogramming Ruby 2 if
| you find yourself refactoring a lot of Ruby. It's out of
| print, but ebooks are available.
| vidarh wrote:
| Just to clarify here: Both lambdas and procs are Proc
| objects. Blocks gets turned into Proc objects when you
| take their value.
|
| So just be clear about whether you're talking about a
| proc or a Proc...
|
| > In a block or Proc, it returns from the calling method
|
| No, in block or a Proc it returns _from the scope where
| it was defined_.
|
| Usually this is the same, and so it doesn't usually
| matter much, but if you pass a proc or a block down to
| another method, then a return within the block will still
| return from the method where it was defined.
|
| This can occasionally be useful, as you can use it to
| pass in a predicate to a method that can control if/when
| you want to return, irrespective of how deeply nested.
| vidarh wrote:
| Too late to edit now, and this is what I get for
| quibbling about casing:
|
| > No, in block or a Proc it returns from the scope where
| it was defined.
|
| Should of course read:
|
| > No, in a block or a proc it returns from the scope
| where it was defined.
| Mystery-Machine wrote:
| This sounds like a really innovative idea. I haven't seen a
| dedicated place for "collection of useful procs", but one
| emerging pattern is to use `app/services` and then have a
| bunch of single-responsibility service classes that each
| have call or perform method and then you use the service
| when you need some shared functionality. It's like a proc,
| but instead it's a class with `#call` method.
| vidarh wrote:
| > It's like a proc, but instead it's a class with `#call`
| method.
|
| It's called the "callable" pattern.
| somewhereoutth wrote:
| Interestingly, in the Lambda Calculus, where everything is a
| function, a standard representation for a natural number n
| (i.e. a whole number >= 0), is indeed a function that
| 'iterates' (strictly, folds/recurses) n times.
|
| E.g. 3:
|
| (f, x) => f(f(f(x)))
| vidarh wrote:
| This is your example in Ruby: isLarge = ->
| {|a| a > 100 } numbers.filter(&isLarge)
|
| Or you could replace the first line: isLarge
| = -> { _1 > 100 }
|
| Some people hate that syntax, though. I think for trivial
| predicates like this, it's fine.
| inopinatus wrote:
| For this audience it may be worth noting that Ruby's blocks are
| closures and are passed to methods either anonymously/implicitly
| or as a named parameter, may be subsequently passed around to any
| collaborator object, or otherwise deferred/ignored, have the same
| range of argument arity as methods and lambdas, can even be
| formed from (and treated similarly to) lambdas, and are thereby
| fundamental to Ruby's claim to being a multiparadigm language
| even as they also betray the Smalltalk roots.
|
| 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.
| rubyfan wrote:
| These were some of the best long running sentences I've read in
| a while. A true rubyist!
| rubybaby99 wrote:
| > A true rubyist!
|
| When it's all a single paragraph, you'll know they've
| achieved Rubynirvana.
| dragonwriter wrote:
| > can even be formed from (and treated similarly to) lambdas
|
| They are also used to create lambdas (even the shorthand
| stabby-lambda syntax desugars to a call to Kernel#lambda with a
| block.)
|
| > Ruby does separately have callcc for more precise control
| over stack unwinding, although it's a little known feature.
|
| callcc is included in CRuby but has been sidelined from Ruby as
| a language separate from CRuby as an implementation for a
| while, with Fibers understood to cover the most important use
| cases for callcc.
| teaearlgraycold wrote:
| This level of cuteness and obsession with syntax is partly what
| drives me away from Ruby. A function should just be a function.
| We don't need to make programming languages look more like
| English. There are certainly issues with other languages and
| their verbosity (like with Java). But I don't want to have more
| ways to do the same thing and worry about how poetic my code
| reads as English - we should worry how poetic it reads as
| _computer code_.
|
| 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 wrote:
| I think Ruby teaches a sense of style, but I'm not sure that
| style carries over to other languages. Python was my primary
| language for 12 years but I'm disappointed in the depth of
| Knowledge python Devs have. Most barely understand the language
| or try to. Ruby seems to coax people into coding into a ruby
| way. I like that.
| teaearlgraycold wrote:
| > but I'm disappointed in the depth of Knowledge python Devs
| have
|
| Not super relevant, but my favorite fun fact about Python
| (specifically CPython) is that hash(x) where x is an int is
| always the value of the int - _except_ for -1, because the
| underlying C function uses -1 to represent errors. So
| hash(-1) == -2, which means you have a hash collision right
| around 0 where hash(-1) == hash(-2)
| shevy-java wrote:
| Blocks yield a lot more flexibility to ruby. It was the primary
| reason why they are so well-appreciated.
| politelemon wrote:
| It's only more readable if you already understand it. Otherwise
| it is not, it requires the same kind of hand waving that happened
| at the start.
|
| Important to understand that readability doesn't mean it should
| be closer to natural language, in programming it means that a
| junior dev troubleshooting that code later down the line can
| easily understand what's happening.
|
| The python examples are certainly more readable from a
| maintainability and comprehension standpoint. Verbosity is not a
| bad thing at all.
| mcphage wrote:
| > It's only more readable if you already understand it.
| Otherwise it is not, it requires the same kind of hand waving
| that happened at the start.
|
| Yes, but it's a core language feature, so if you spend any time
| programming Ruby, you'll come to understand it.
| varispeed wrote:
| With autocomplete being so good today, do we really need
| languages with cryptic syntax that obscure what is going on?
| Alifatisk wrote:
| To an outsider watching Ruby, it's cryptic, esoteric and maybe
| magical. But when you actually use it and learn mechanism
| underneath it, things start to make sense. However, Rubys
| dynamic architecture have also made it difficult for DX. Things
| like autocomplete barely works in the ecosystem, because it's
| so unpredictable until runtime
| Alifatisk wrote:
| I believe the underlying behaviour of Ruby blocks is one of those
| mechanics that isn't talked about that much for newcomers, they
| just get used to how Ruby code look like when they see Rails,
| Cucumber or RSpec
|
| Blocks and method_missing is one of those things in Ruby is what
| makes it so powerful! I remember watching a talk where someone
| was able to run JS snippets on pure Ruby just by recreating the
| syntax. That proves how powerful Ruby is for creating your own
| DSL
|
| It's also a double edged sword and something you have to be
| careful with on collaborative codebases. Always prefer simplicity
| and readability over exotic and neat tricks, but I understand the
| difficulty when you have access to such a powerful tool
| nasmorn wrote:
| IMO blocks are not something to be careful about in ruby. If
| you don't use blocks you are the weirdo in this language.
|
| Method missing is a different beast altogether. I would
| probably avoid it nowadays.
| vidarh wrote:
| There are legitimate uses of method_missing (though often
| you'd want to call define_method dynamically instead for
| performance), but they tend to be when you use an object as a
| proxy for something (say a remote API) where you genuinely
| can't know the set of messages the object might need to be
| able to receive.
| dorianmariecom wrote:
| blocks are just procs :)
| Alifatisk wrote:
| It does look like it but procs encapsulates a block, but a
| block alone is not a proc
| kaiuhl wrote:
| That's just a performance optimization because blocks are
| typically only invoked, not passed or otherwise manipulated.
| If you want to do anything with the block, you pay a tiny tax
| and have a Proc instance.
| SafeDusk wrote:
| I recommend reading Shopify CEO Tobi's try[0] for good example of
| how Ruby's block behavior and meta-programming makes it easy to
| create a single file, shell wrapper.
|
| [0]: https://github.com/tobi/try/blob/main/try.rb
| codeduck wrote:
| what the fuck...
| itsme0000 wrote:
| Market forces will ensure this system isn't utilized regardless
| of how good it is. Ruby is simply to similar to Python to
| consider training people solely in that.
|
| It's a hard reality because I'm sure Ruby is better according to
| some criteria, but be realistic their share of the market is
| going to shrink until it's not really an option of most large
| companies.
|
| I know people will disagree with me, but I wish I knew earlier in
| my Carr how little this sort of thing matters.
| an0malous wrote:
| Let's check back in 6 months after the AI bubble pops
| mistercheph wrote:
| Thankfully, programming languages are subject to strong network
| effects but not massive ones, Ruby will continue to be a niche
| language that has active users and projects for many more years
| than you expect.
| Dan42 wrote:
| This is really cute and heartwarming.
|
| Back in the day, a lot of people including me reported feeling
| more comfortable in Ruby after one week than all their other
| languages with years of experience, as if Ruby just fits your
| mind like a glove naturally.
|
| I'm glad new people are still having that "Ruby moment"
| nixpulvis wrote:
| Ruby is still the first tool I reach for if I need to do
| something quickly and naturally. People are always surprised at
| how readable the code comes out even compared to python.
| codeduck wrote:
| The three stages of ruby:
|
| 1. Someone scoffs at you for writing ruby instead of python /
| go / javascript / kotlin
|
| 2. Then they read the code
|
| 3. Then they install ruby
|
| It's an amazing language and remains my go-to for anything
| that doesn't need protocol stuff.
| rvitorper wrote:
| I can't unsee it either. Will try it later
| leemcalilly wrote:
| Welcome
| thepaulmcbride wrote:
| Ruby is really let down by the tooling around the language. The
| language itself would be so much more fun to write if the lsp
| would reliably jump to the definition of functions etc that seem
| to appear out of no where. It has been the biggest source of
| frustration for me while learning Ruby.
| stonecharioteer wrote:
| I'm hoping the new age tooling that's coming out is going to
| make things better. I want to contribute to rv personally.
| Mystery-Machine wrote:
| You're right! Although I get faily far by using Bust-a-gem VS
| Code extension. (The underlying ripper-tags gem can work with
| any IDE) https://github.com/gurgeous/bust-a-gem
|
| I have an "on save" hook that runs ripper-tags on every file
| save. This keeps the definitions always up to date.
| mmcromp wrote:
| I say the opposite, the lack of tooling highlights the weakness
| of the language. The drive to make it declarative/mimic
| "natural" language by reshuffling and overloading can be
| "delightful" to some, but beyond the paper covered surface is a
| mess. And for what ? "5.times do something unless" isn't cute
| to me. It's a dog "talking" by putting peanut butter in his
| mouth. But I think I'm the only one who feels this way
| Bolwin wrote:
| And it's twice as bad on windows.
| Mystery-Machine wrote:
| Ruby is beautiful.
|
| It's weird, and different and therefore a bit repulsive (at least
| to me it was) at first. But, once you learn it, it's so easy to
| read it and to understand what's going on.*
|
| * Side-note: Sometimes variables or methods look the same as
| parenthesis () are optional. So, yes, there's more things that
| can look like magic or be interpreted in multiple ways, but more
| times than not, it helps to understand the code faster, because
| `clients` and `clients()` (variable or method) doesn't matter if
| all it does is "get clients" and you just need to assume what's
| stored/returned from that expression. Also "get clients" can be
| easily memoized in the method implementation so it gets as close
| as possible to being an actual variable.
| t-writescode wrote:
| Ruby Blocks and how they're used literally everywhere is one of
| the hallmarks of why it's so nice to write the first time in that
| language; and it shaped how I use and select future languages.
|
| Ruby Blocks are almost certainly the reason why I love Kotlin so
| much - it feels like a well-typed, curly-bracket-styled Ruby in
| those ways. The collection operation chains in both languages
| just. feel. good. And I blame Ruby for my first exposure to them,
| and possibly a lot of people's early exposure to them that helped
| languages that came after become better.
|
| Ruby does take it to a-whole-nother level though, in particular
| with its 'space as separator' syntax, so you can make a *robust*
| DSL that's even more powerful than Kotlin's "if the last param is
| a function, you can just put a curly bracket and go" style.
| user3113 wrote:
| Ruby is my favorite language. There is no other language with
| same ergonomics. I dont understand hype around ts and all
| frameworks that changes every week.
| username223 wrote:
| Was this human-generated? Clearly it's self-submitted, and the
| useless "hero image" looks like lazy AI slop with Pixar
| characters, but did a human generate the text?
| bashkiddie wrote:
| I heard some rumor that ruby was a type 2 lisp. There were some
| guys who rate programming languages on how much lisp features
| they reassemble. What is the source?
| rockyj wrote:
| Ruby is still so good to read and hack things with. It is a shame
| that it is not so popular and you know the reasons why. I still
| wish with such a good DSL friendly structure, it should have
| become an IaC de-facto standard language or used in some niche
| where I could use it freely without question.
| rapind wrote:
| It was revolutionary for readability and has influenced a lot
| of newer languages. That's a pretty good legacy IMO.
| culi wrote:
| The biggest thing holding Ruby back is lack of gradual typing
| imo. I honestly think javascript is a better fit for IaC. Not
| only is already the language of the web (everyone has to know
| _some_ javsacript) but JSDOC is supported by most IDEs giving
| it gradual typing. Many people don 't realize jsdoc _is_
| typescript and is a full replacement.
|
| Nowadays I like to reach for Julia for quick one-off scripts
| and webscraping. It has a beautiful and accessible syntax with
| built-in gradual typing. I would love to see it more widely
| adopted in the IaC world
___________________________________________________________________
(page generated 2025-10-18 23:01 UTC)