1. 1

    Non-Ruby developer here. Is there any difference between a Ruby yield and just passing in a lambda as an argument?

    1. 2

      Yes, though there are a few aspects in play here. First, a block parameter is a special argument and in order to pass a lambda in as a block parameter you must use the & operator (this operator will attempt to coerce non-proc objects to procs, so this is why symbols are sometimes used in this way).

      When you have a block in ruby, the lifetime of the lexical context is tied to the call-frame that is created upon invocation. This might sound like an implementation detail but it allows the runtime to avoid allocating a proc object to hold the closed over lexical environment (aka. faster and lighter). Generally, if you know you’re receiving a block, it can be an advantage to stick with yield and the implicit parameter rather than pull out the proc as a value and use #call (or an alias of #call like #[]) on the object (this has been optimized more recently in some cases to allow lazy allocation of the object if you only forward it to another call).

      The other difference is around parameter behavior. Blocks and Procs don’t require the arity of the block and the yield to match. So you can take fewer or more arguments than are passed and the extra fields will either be dropped or set to nil. Lambdas however, require the arity to match, much like a method call (distinctly constructed using lambda or -> “stabby” syntax). This can be a good thing but I’ll avoid writing half an article here.

      One more advanced case that many are unaware of, you can capture a proc using the constructor without a block, so these end up working in a similar way:

      def a_is_42(a, &callable)
        callable.call if a == 42 && callable
      end
      
      def b_is_42(b)
        Proc.new.call if b == 42 && block_given?
      end
      

      Of course, it’s a contrived example on the second because we could just use yield there, but it does show that we can build the proc object lazily which can be a big win in some hot paths for Ruby code. There are some more optimization techniques but this gives a little taste of the range of differences between the proc world and the block world. If people are curious, I can write up more about this stuff.

      TL;DR, you can avoid a lot of allocation and call overhead by keeping things in block form.

      1. 1

        That makes sense, thanks!

      2.  

        The only difference is that your lambda will be an instance of the class Proc which is in charge of storing and executing the piece of code associated to your lambda.

        It’s a bit more complicated than this, but let’s keep it simple if you’re not familiar with Ruby. ;-)

        I invite you to read this article to dive into the specificities of the Proc class and lambdas in Ruby.

        1.  

          Which article?

      1. 1

        Agree with you. Sidekiq is a great example of this. :-)

        They’re also very useful for app configuration and DSLs.

        1. 10

          Nice write up on the OpenStruct object in ruby!

          However, I have to do my due diligence here. For most (all?) cases, using OpenStruct is a terrible anti-pattern. Any OpenStruct object will respond to any method you call on it and if it doesn’t exist, returns nil. This causes uncertainty within the system and makes testing a nightmare.

          As I recommend to most people, you’re better off using a PORO (plain old ruby object)!

          1. 10

            And anima makes it very easy to avoid OpenStruct/Struct.

            1. 1

              Nice! TIL!

            2. 3

              Using OpenStruct can also kill your performance due to effects it has on the global method cache.

              1. 2

                I thought so too, until a friend corrected me: That was fixed several years ago and isn’t still the case.

              2. 2

                Thanks for sharing your precious experience with OpenStruct :-)

                Anyway, the goal of my articles is just to describe a notion as deeply as possible. I prefer to keep a neutral point-of-view because each notion that I describe can be useful in many cases.

                Implementing the notion in real use cases is just a matter of sensitivity for each developer.

                That’s why I’m glad that you share your opinion and your experience with the OpenStruct class here.

                1. 2

                  I like reading about all the nooks and crannies about Ruby so your articles are very interesting to me! Thanks for sharing.

                  1. 1

                    Why do you want that neutral position? One of the things I really love in articles is seeing how people evaluate the benefits and risks of tools like OpenStruct. I get to understand when to use or not use something, learn general strategies useful for other tools. Let the documentation be non-judgemental description, I want experienced opinions. :)

                    1. 1

                      Actually my articles are made to demystify a misunderstood or opaque notion. By doing so, developers can take the decision to apply the notion or not by knowing all the aspects of it.

                      Also, this enhances their general culture about the language.

                      After, your point of view is also correct. Maybe, in a near future I’ll start to share my experience with the notions that I treat in my articles.

                      Thanks for the feedback. It’s really appreciated :-)

                1. 3

                  It’s worth noting that some people consider inheriting from Exception rather than StandardError to be bad style. Because catching Exception includes things like ScriptError::SyntaxError and SignalException::Interrupt (^C), the advice seems to be to catch StandardError instead if you don’t know what exceptions to expect and thus your custom exception classes would need to inherit from StandardError to match that.

                  1. 3

                    Yep, and some common libraries (looking at you, nokogiri) don’t follow this advice and instead throw things like SyntaxError that should be limited to the Ruby parser itself.

                    1. 0

                      Actually, It’s better to create a custom error that makes sense within the execution context.

                      Anyway, Thank you for the precision. :-)

                      But the purpose of my articles are just to explain how it works, not how to use it.

                      I believe in the fact that developers are not robots and they’ll learn all the aspects of a notion by using it, repeatedly. Instead of telling them what’s good or not, I prefer let them figure it out by themselves.

                      I prefer put all of my energy on explaining the main concept and let the 5% of edge-cases on side.

                      But, again, thanks for your precious feedback :-)