Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Extending Ruby with Ruby

Extending Ruby with Ruby

From Railsconf 2012

Michael Fairley

April 25, 2012
Tweet

More Decks by Michael Fairley

Other Decks in Programming

Transcript

  1. Michael Fairley @michaelfairley github.com/michaelfairley My name is Michael Fairley, and

    I’m on Twitter and GitHub very cleverly as michaelfairley.
  2. I work at 1000memories. We help families, groups of friends,

    and organizations digitize and organize old their photos and make sure the people who care about them get to see them.
  3. We have an iPhone app called ShoeBox that makes it

    really it really to scan old paper photos.
  4. And then we automatically crop to those edges (even if

    they’re rotated or tilted), and you can rotate and tweak the colors a bit, and you end up with a great looking photo, without the hassle of a big flatbed scanner and desktop scanning software.
  5. I’ve gotten to learn a bit about my family as

    my parents and grandparents have used ShoeBox to scan some of our family’s old photos. This is my grand-grandmother who I grew up calling Ninna, but a few weeks ago I learn that her real name is...
  6. Extending Ruby with Ruby So, “Extending Ruby with Ruby”, what’s

    that mean? Ruby’s metaprogramming tools are a lot more powerful than we give them credit for.
  7. def initialize(attributes = {}) attributes.each do |name, value| if respond_to?(“#{name}=”)

    public_send(“#{name}=”, value) end end end We typically think of them in the context of saving ourselves some typing and make our code more DRY.
  8. def image_file image = open(@uri) # open-uri’s result doesn’t have

    # original_filename if the # file is over 20k def image.original_filename @uri.path.split('/').last end image end Or to monkey patching a library that doesn’t quite work as we need it to.
  9. Extending Ruby with Ruby But we can use metaprogramming to

    add new features to Ruby itself, features that other languages’ core teams had to endlessly debate and eventually code directly into the language’s compiler or runtime. But with Ruby, we can just use Ruby itself to extend the language, thus, extending Ruby with Ruby.
  10. I’m going to take a feature from each of three

    programming languages and show you why and how it can be added to Ruby. Some of these ideas are good, some are bad, and some are flat out crazy.
  11. Function decorators Python has these things called decorators, which are

    a little bit of syntactic sugar for adding commonly reused tidbits of functionality to methods and functions. I’m going to take a few minutes to show some examples that demonstrate what decorators are and why they would be useful in Ruby. I used to do a lot of Python coding, and function decorators are something that I definitely miss from those day, and thing are something that can help almost all of us make our Ruby code cleaner.
  12. def send_money(from, to, amount) from.balance -= amount to.balance += amount

    from.save! to.save! end In Ruby, let’s pretend we want to send money from one bank account to another. Fairly simple right?
  13. def send_money(from, to, amount) from.balance -= amount to.balance += amount

    from.save! to.save! end We subtract the amount to transfer from the `from` account’s balance...
  14. def send_money(from, to, amount) from.balance -= amount to.balance += amount

    from.save! to.save! end Add it to the `to` account’s balance...
  15. def send_money(from, to, amount) from.balance -= amount to.balance += amount

    from.save! to.save! end And then save both of the accounts. But, there’s a couple things that are wrong with this, the most notable of which is the lack of a transaction. (If `from.save!` succeeds, but `to.save!` fails, money will vanish into thin air).
  16. def send_money(from, to, amount) ActiveRecord::Base.transaction do from.balance -= amount to.balance

    += amount from.save! to.save! end end Luckily, ActiveRecord (or however else you’re using your database, hopefully) makes this easy. We just throw this transaction block around our code, and we are guaranteed that everything in it succeeds, or everything in it fails.
  17. def send_money(from, to, amount): from.balance -= amount to.balance += amount

    from.save() to.save() Let’s look at the same example in Python. The transactionless version looks almost identical to the equivalent Ruby.
  18. def send_money(from, to, amount): try: db.start_transaction() from.balance -= amount to.balance

    += amount from.save() to.save() db.commit_transaction() except: db.rollback_transaction() raise Once we add the transaction though, things get ugly. There are 10 lines of code in this method...
  19. def send_money(from, to, amount): try: db.start_transaction() from.balance -= amount to.balance

    += amount from.save() to.save() db.commit_transaction() except: db.rollback_transaction() raise but only 4 of them are actually domain logic.
  20. def send_money(from, to, amount): try: db.start_transaction() from.balance -= amount to.balance

    += amount from.save() to.save() db.commit_transaction() except: db.rollback_transaction() raise The other 6 are all boilerplate for running the main logic inside a transaction. Worse than this being ugly and verbose though, is that every time you need to use a transaction, you have to remember all 6 of these lines, including the proper error handling and rollback semantics. This is exactly what Rich Hickey talked about on Monday. This is a great example of complecting things together. So how can we make this prettier and more DRY? Python doesn’t have blocks, so what we did with Ruby isn’t really an option.
  21. def send_money(from, to, amount): from.balance -= amount to.balance += amount

    from.save() to.save() send_money = transactional(send_money) What python does provide us though, is the ability to pass around and reassign methods easily. So, what we can do is write a function called `transactional` that takes a function as an argument, and returns the same function, but wrapped in the transaction boilerplate.
  22. def transactional(fn): def transactional_fn(*args): try: db.start_transaction() fn(*args) db.commit_transaction() except: db.rollback_transaction()

    raise return transactional_fn It takes a function (send_money in our example) as its only argument.
  23. def transactional(fn): def transactional_fn(*args): try: db.start_transaction() fn(*args) db.commit_transaction() except: db.rollback_transaction()

    raise return transactional_fn The new function has all of the boilerplate for wrapping the business logic in a transaction
  24. def transactional(fn): def transactional_fn(*args): try: db.start_transaction() fn(*args) db.commit_transaction() except: db.rollback_transaction()

    raise return transactional_fn And inside of the boilerplate, it calls the original function that was passed in, with the args that were passed in to the new function
  25. def send_money(from, to, amount): from.balance -= amount to.balance += amount

    from.save() to.save() send_money = transactional(send_money) So, we pass the function `send_money` to the `transactional` function we just defined, and returns a new function that does everything `send_money` does, except wrapped inside of a transaction, and then we assign this new function to `send_money`, overriding its original value. Now, whenever we call `send_money`, the version with the transaction will get executed.
  26. @transactional def send_money(from, to, amount): from.balance -= amount to.balance +=

    amount from.save() to.save() And here’s what all of this has been leading up to. This idiom is so common in Python that special syntax got added to support it. This is a function decorator. And this is pretty much how you make something transactional with the Django ORM.
  27. So what? So you’re thinking “So what? You just showed

    that this decorator mumbo jumbo solves the same problems that blocks solve. Why would we want this in Ruby.” Well, let’s look at a case where blocks are inelegant.
  28. def fib(n) if n <= 1 1 else fib(n -

    1) * fib(n - 2) end end So we have a very naive method to calculate the value of the nth element in the fibonacci sequence. This is slow, so we want to memoize it.
  29. def fib(n) @fib ||= {} @fib[n] ||= if n <=

    1 1 else fib(n - 1) * fib(n - 2) end end The common way to do this is to shove this `||=` stuff all over the place, which suffers from the same problem as the first example of transactions: we’ve mixed our core algorithm and some additional behavior we want to surround it.
  30. def fib(n) @fib ||= {} return @fib[n] if @fib.has_key?(n) @fib[n]

    = if n <= 1 1 else fib(n - 1) * fib(n - 2) end end We’ve also forgotten a few things, like the fact that `nil` and `false` can’t be memoized this way. More boilerplate that we have to remember and type ourselves.
  31. def fib(n) memoize(:fib, n) do if n <= 1 1

    else fib(n - 1) * fib(n - 2) end end end So, we could solve this with a block, but blocks don’t have access to their calling method’s name or arguments, so we have to pass them in manually.
  32. def fib(n) memoize(:fib, n) do time(:fib, n) do if n

    <= 1 1 else fib(n - 1) * fib(n - 2) end end end end And we see that as we start adding more wrappers around this method’s functionality...
  33. def fib(n) memoize(:fib, n) do time(:fib, n) do synchronize(:fib) do

    if n <= 1 1 else fib(n - 1) * fib(n - 2) end end end end end We keep retyping the name of the method and it’s arguments over and over. This is quite brittle and will break once we change the method’s signature in any way.
  34. def fib(n) if n <= 1 1 else fib(n -

    1) * fib(n - 2) end end ActiveSupport::Memoizable.memoize :fib So, we’ve solved it by putting things like this after the end of our methods.
  35. def fib(n) ... end ActiveSupport::Memoizable.memoize :fib def fib(n): ... fib

    = memoize(fib) Which should remind you of what we saw in Python, when the modification to the method came after the method itself. Why did the Python community not like this? A few reasons: 1) You can no longer trace the execution of your code from top to bottom 2) It’s too easy to move a method around and forget to bring the code that comes after it
  36. def fib(n): if n <= 1: return 1 else return

    fib(n - 1) * fib(n - 2) So let’s look at this fibonacci example in Python. We want to memoize it,
  37. @memoize def fib(n): if n <= 1: return 1 else

    return fib(n - 1) * fib(n - 2) So we decorate it with memoize.
  38. @time @memoize def fib(n): if n <= 1: return 1

    else return fib(n - 1) * fib(n - 2) And if we want to time it,
  39. @synchronize @time @memoize def fib(n): if n <= 1: return

    1 else return fib(n - 1) * fib(n - 2) or synchronize the calls to it, we just add another decorator. And that’s the end of that story.
  40. +Synchronized +Timed +Memoized def fib(n) if n <= 1 1

    else fib(n - 1) * fib(n - 2) end end So, I’m going to take the next 10 minutes to show you how we get this in Ruby. (using + instead of @, and starting with a capital letter). And what’s really cool, is that we can add this decorator syntax that is very close to Python’s, to Ruby with, with only about 15 lines of code.
  41. +Transactional def send_money(from, to, amount) from.balance -= amount to.balance +=

    amount from.save! to.save! end So, back to our favorite `send_money` example. We want to add this `Transactional` decorator.
  42. class Transactional < Decorator def call(orig, *args, &blk) ActiveRecord::Base.transaction do

    orig.call(*args, &blk) end end end `Transactional` is a subclass of `Decorator`, which we’ll talk about in a second.
  43. class Transactional < Decorator def call(orig, *args, &blk) ActiveRecord::Base.transaction do

    orig.call(*args, &blk) end end end It has one method `call`, that will get called in place of the original method. It takes the method it’s wrapping, and the args and block that are being passed to that method in it’s current invocation.
  44. class Transactional < Decorator def call(orig, *args, &blk) ActiveRecord::Base.transaction do

    orig.call(*args, &blk) end end end And then we call the original method, inside of the transaction block. You’ll notice this getup is a little different than how Python’s decorators work. Instead of the decorator generating a function that’s receives the arguments, our ruby decorators will receive the method and it’s arguments on each invocation. We have to do this due to Ruby’s method binding semantics, which we’ll talk about a bit more in a second.
  45. class Decorator def self.+@ @@decorator = self.new end def self.decorator

    @@decorator end def self.clear_decorator @@decorator = nil end end So what’s in Decorator? This `+@` thing is the unary plus operator, so this method will be called when we call +DecoratorName, like we did with +Transactional a few slides back.
  46. class Decorator def self.+@ @@decorator = self.new end def self.decorator

    @@decorator end def self.clear_decorator @@decorator = nil end end We need a way to get the current decorator
  47. class Decorator def self.+@ @@decorator = self.new end def self.decorator

    @@decorator end def self.clear_decorator @@decorator = nil end end And a way to clean the current decorator
  48. class Bank extend MethodDecorators +Transactional def send_money(from, to, amount) from.balance

    -= amount to.balance += amount from.save! to.save! end end A class that wants to have decorated methods needs to extend MethodDecorators. extend is similar to include, but instead of the methods on the module becoming instance methods of the class they’re extended onto, they become methods on the class itself. We could’ve extended MethodDecorators directly onto `Class`, but I think it’s a best practice to leave a decision like that up to the end user.
  49. module MethodDecorators def method_added(name) super decorator = Decorator.decorator return unless

    decorator Decorator.clear_decorator orig_method = instance_method(name) define_method(name) do |*args, &blk| m = orig_method.bind(self) decorator.call(m, *args, &blk) end end end
  50. module MethodDecorators def method_added(name) super decorator = Decorator.decorator return unless

    decorator Decorator.clear_decorator orig_method = instance_method(name) define_method(name) do |*args, &blk| m = orig_method.bind(self) decorator.call(m, *args, &blk) end end end method_added is a private instance_method on class that is called whenever a method is define, giving us a nice way to hook into a method’s creation.
  51. module MethodDecorators def method_added(name) super decorator = Decorator.decorator return unless

    decorator Decorator.clear_decorator orig_method = instance_method(name) define_method(name) do |*args, &blk| m = orig_method.bind(self) decorator.call(m, *args, &blk) end end end Call super. It’s easy to forget to this in when you’re overriding a method like method_added, method_missing, or respond_to?, but when you don’t do this, you break other libraries and
  52. module MethodDecorators def method_added(name) super decorator = Decorator.decorator return unless

    decorator Decorator.clear_decorator orig_method = instance_method(name) define_method(name) do |*args, &blk| m = orig_method.bind(self) decorator.call(m, *args, &blk) end end end We get the current decorator, returning if there isn’t one, and then we clear it out. It’s important to clear it out early, because we redefine the method below, which then triggers method_added again.
  53. module MethodDecorators def method_added(name) super decorator = Decorator.decorator return unless

    decorator Decorator.clear_decorator orig_method = instance_method(name) define_method(name) do |*args, &blk| m = orig_method.bind(self) decorator.call(m, *args, &blk) end end end We extract the original version of the method
  54. module MethodDecorators def method_added(name) super decorator = Decorator.decorator return unless

    decorator Decorator.clear_decorator orig_method = instance_method(name) define_method(name) do |*args, &blk| m = orig_method.bind(self) decorator.call(m, *args, &blk) end end end And then redefine it
  55. module MethodDecorators def method_added(name) super decorator = Decorator.decorator return unless

    decorator Decorator.clear_decorator orig_method = instance_method(name) define_method(name) do |*args, &blk| m = orig_method.bind(self) decorator.call(m, *args, &blk) end end end instance_method above actually returns an UnboundMethod, which is a method that doesn’t know what object it belongs to, so we have to rebind it to the current object.
  56. module MethodDecorators def method_added(name) super decorator = Decorator.decorator return unless

    decorator Decorator.clear_decorator orig_method = instance_method(name) define_method(name) do |*args, &blk| m = orig_method.bind(self) decorator.call(m, *args, &blk) end end end And then we call the decorator with the original method and the arguments that were passed to the new version.
  57. What else? And of course, there’s always a handful of

    incredibly important corner cases that need to be tackled before this code can be considered production ready.
  58. Multiple decorators +Timed +Memoized def fib(n) ... end The implementation

    I just showed you only allowed one decorator per method, but we want to be able to use more than one.
  59. Visibility private +Transactional def send_money(from, to, amount) ... end define_method

    defines public methods, but we want private and protected methods that are decorated to keep their visibility.
  60. Singleton methods +Memoize def self.calculate ... end method_added and define_method

    only work for instance methods, so more work is needed to get singleton methods (and consequently class methods) working.
  61. Arguments +Retry.new(3) def post_to_facebook ... end In the python example,

    I showed that we could pass a value to a decorator. We want to be able to construct individual instances of decorators however we want and use them to decorate our methods
  62. gem install method_decorators http://github.com/ michaelfairley/ method_decorators I’ve tackled all these

    corner cases, added a thorough test suite, and rolled all of this into a gem. `gem install method_decorators`, or check the code out on github. Use this, as I think it can make a bunch of your code cleaner and more easily read and maintained.
  63. ruby > python??? So now whenever you’re arguing about whether

    ruby or python is better, you can point out that we can add features from python to ruby, but ruby features cannot be added to python. So I think we can now definitively say that ruby is better than python.
  64. ruby > python??? And then of course, you’ll need to

    plug your ears and go “lalalala” when the pythonista starts talking about numpy and first class functions and so on.
  65. Partial application Scala’s got this nice thing called partial application.

    Partial application is a general functional programming technique that’s in a bunch of languages, but Scala’s support and syntax for it is particularly elegant.
  66. [1,2,3].map{ |i| i.to_s } When we have a block like

    this in Ruby, that just calls a method its only argument, we can shorten it to look like...
  67. List(1,2,3).map(i => i.toString) Here’s the same thing in Scala. That

    first `i` is the argument to a function that’s being created and `i.toString` is its body.
  68. List(1,2,3).map(_.toString) Scala lets us cut out specifying the argument at

    all, and instead just use an _, and it fills it in with the argument passed to the function. I find this to be much more readable/easy to explain that our Symbol#to_proc equivalent.
  69. List(1,2,3).map(i => i + 1) List(1,2,3).filter(i => i * 2

    < 3) List(1,2,3).reduce((i, j) => i + j * 2) List(1,2,3).map(i => 1 + i) But what’s cool, is that for more complex anonymous functions like these,
  70. List(1,2,3).map(_ + 1) List(1,2,3).filter(_ * 2 < 3) List(1,2,3).reduce(_ +

    _ * 2) List(1,2,3).map(1 + _) We can partially apply all of the arguments with an _ as well.
  71. [1,2,3].map{ |i| i + 1 } [1,2,3].select{ |i| i *

    2 < 3 } [1,2,3].reduce{ |i, j| i + j * 2 } [1,2,3].map{ |i| 1 + i } With these same functions in Ruby...
  72. [1,2,3].map( ??? ) [1,2,3].select( ??? ) [1,2,3].reduce( ??? ) [1,2,3].map(

    ??? ) We don’t have a way of not explicitly passing the arguments to these.
  73. [1,2,3].map(& _ + 1) [1,2,3].select(& _ * 2 < 3)

    [1,2,3].reduce(& _ + _ * 2) [1,2,3].map(& 1 + _ ) We can do this in Ruby with just an added ampersand (+ some magic code), but this only works if the _ is the first thing in function that you’re writing. There’s also a bunch of cases where the code I’ve written for this doesn’t really work, so I’m not going to waste your time showing how’ve I’ve done this.
  74. [1,2,3].map(&_{ _ + 1 }) [1,2,3].select(&_{ _ * 2 <

    3 }) [1,2,3].reduce(&_{ _ + _ * 2 }) [1,2,3].map(&_{ 1 + _ }) But, with a little bit of added ugliness, we can get things to work exactly like they do in Scala. This has definitely crossed the threshold into “bad idea” territory where it’s not generally useful, but I think the “how” behind this is interesting enough to look into.
  75. [1,2,3].map(&_{ _ + 1 }) What’s happening here is that

    we’re passing a block to a method called
  76. [1,2,3].map(&_{ _ + 1 }) And then the & calls

    to_proc on the result of that `_` method, resulting in a lambda that does some trickery
  77. class Object def _(&blk) PartialApplication.new(blk) end end So this is

    the _ method, which we added to Object. (Don’t do this. If you’re adding a method to Object, you should reevaluate things a bit.) It returns a new instance of PartialApplication. So what’s a PartialApplication?
  78. class PartialApplicaton def initialize(blk) @blk = blk end def to_proc

    lambda do |*args| Execution.new(args, @blk.binding). instance_eval(&@blk) end end end PartialApplication stores the block it’s given.
  79. class PartialApplicaton def initialize(blk) @blk = blk end def to_proc

    lambda do |*args| Execution.new(args, @blk.binding). instance_eval(&@blk) end end end When to_proc is called on it, it returns a lambda
  80. class PartialApplicaton def initialize(blk) @blk = blk end def to_proc

    lambda do |*args| Execution.new(args, @blk.binding). instance_eval(&@blk) end end end that builds a new `Execution` with the arguments that the block is given, and the binding of the block. A block’s binding is the environment that the block was defined in, so we’ll us it later to pull some things out of.
  81. class PartialApplicaton def initialize(blk) @blk = blk end def to_proc

    lambda do |*args| Execution.new(args, @blk.binding). instance_eval(&@blk) end end end Finally, we instance_eval the block on the Execution. So what’s an execution?
  82. class PA::Execution < BasicObject def initialize(args, _binding) @args = args

    @binding = _binding end ... end Execution unsurprisingly takes the arguments and binding it was given, and stores them.
  83. class PA::Execution < BasicObject def initialize(args, _binding) @args = args

    @binding = _binding end ... end Execution unsurprisingly takes the arguments and binding it was given, and stores them.
  84. class PA::Execution < BasicObject ... def method_missing(name, *args, &blk) if

    name == :_ @args.shift else @binding.instance_eval do method(name) end.call(*args, &blk) end end end And remember that we instance_evaled that block on this method, and obviously there’s not really anything defined in here, so every method call should go to method_missing instead.
  85. class PA::Execution < BasicObject ... def method_missing(name, *args, &blk) if

    name == :_ @args.shift else @binding.instance_eval do method(name) end.call(*args, &blk) end end end Hitting the else branch first, we find the method that was trying to be called in the original binding, and then we call it.
  86. class PA::Execution < BasicObject ... def method_missing(name, *args, &blk) if

    name == :_ @args.shift else @binding.instance_eval do method(name) end.call(*args, &blk) end end end But, when the name of the method is _, we instead pop the first element off of the args that were passed in in the invocation of the PartialEvaluation lambda.
  87. [1, 2, 3].map(&_{ _ + rand }) This `_` generates

    a new instance of PartialApplication that is initialized with the block.
  88. [1, 2, 3].map(&_{ _ + rand }) The & calls

    to_proc on that instance of PartialApplication, and it returns a lambda that gets called with each item we’re mapping over and passes those args to a new instance of Execution.
  89. [1, 2, 3].map(&_{ _ + rand }) That lambda instance

    evals the block on that instance of Execution.
  90. [1, 2, 3].map(&_{ _ + rand }) When `_` is

    called, the Execution pops the first (and only) argument off the args, and returns it.
  91. [1, 2, 3].map(&_{ _ + rand }) And when rand

    is called, it’s evaluated in this scope, returning a random number.
  92. rails_conf :: IO (Network.Stream.Result (Response String)) -> String rails_conf response

    = “I'm lazy" main = putStrLn (rails_conf (simpleHTTP (getRequest “http://haskell.org/"))) So, this is some dummy Haskell code to quickly demonstrate this concept, and I promise this is the only slide of Haskell in the deck.
  93. rails_conf :: IO (Network.Stream.Result \ (Response String)) -> String rails_conf

    response = “I'm lazy" main = putStrLn (rails_conf (simpleHTTP (getRequest “http://haskell.org/"))) This declares a function called rails_conf, and says that its argument is going to be of type IO (Network.Stream.Result (Response String)), which is Haskellese for “the result of a network request”, and returns a String.
  94. rails_conf :: IO (Network.Stream.Result (Response String)) -> String rails_conf response

    = “I'm lazy" main = putStrLn (rails_conf (simpleHTTP (getRequest “http://haskell.org/"))) Here we define the body of that function, calling the argument we get passed `response`, and then returning the string “I’m lazy”
  95. rails_conf :: IO (Network.Stream.Result (Response String)) -> String rails_conf response

    = “I'm lazy" main = putStrLn (rails_conf (simpleHTTP (getRequest “http://haskell.org/"))) We define the main function, which is the function that gets called when we run this file, and tell it to print out the result of calling the rails_conf function
  96. rails_conf :: IO (Network.Stream.Result (Response String)) -> String rails_conf response

    = “I'm lazy" main = putStrLn (rails_conf (simpleHTTP (getRequest “http://haskell.org/"))) and we pass `rails_conf` an HTTP request
  97. $ ./rails_conf I’m lazy And again, out pops “I’m Lazy”.

    Either Haskell doesn’t need a network to make HTTP requests, which would be pretty cool, or something else is going. The something else is lazy evaluation: Haskell doesn’t actually execute the http request until the result is needed from it.
  98. def rails_conf(response) “I’m not so lazy” end def get(url) Net::HTTP.get(URI.new(url))

    end puts rails_conf( get(“http://ruby-lang.org”)) Same thing in Ruby. And of course we all know that if we ran this without an internet connection, it would blow up.
  99. def rails_conf(response) “I’m lazy” end def get(url) Lazy.new do Net::HTTP.get(URI.new(uri))

    end end puts rails_conf( get(“http://ruby-lang.org”)) But, we can wrap this in a Lazy, which we’re about to define, and then this method won’t actually evaluate as its result is unused. And so this version of the program will run fine without a network connection.
  100. class Lazy < BasicObject def initialize(&blk) @block = blk end

    def method_missing(method, *args, &blk) @result ||= @block.call @result.send(method, *args, &blk) end end Lazy is incredibly simple. It stores the block that’s passed to it.
  101. class Lazy < BasicObject def initialize(&blk) @block = blk end

    def method_missing(method, *args, &blk) @result ||= @block.call @result.send(method, *args, &blk) end end And then when any methods are called on it, it evaluates the block once, stores the result, and then sends that method on to the result of the block. All future methods called on the lazy are then delegated to the cached result.
  102. def three Lazy.new do puts “2” 3 end end x

    = three puts “1” puts x Quick example: this will print 1, 2, 3
  103. +Lazy def three puts “2” 3 end x = three

    puts “1” puts x And of course, we could define a Lazy decorator that does this for us.
  104. What about the crazy? I said I would show you

    good ideas, bad ideas, and crazy ideas, and I’ve already shown the good and the bad, so now for the crazy.
  105. modules = ObjectSpace.each_object(Module) modules.each do |k| k.instance_methods.each do |m| next

    if ... #some exceptions im = k.instance_method(m) k.send(:define_method, m) do |*args| Lazy.new do im.bind(self).call(*args)} end end end end We loop over every method in every module and class (with a couple of exceptions), and redefine them to be lazy. Now, every method in Ruby is completely lazy, à la Haskell, and if you program in a functional style where you don’t rely on side effects from your methods, this completely works, and I’ve written some simple programs with this change made.
  106. # Controller def index @tweets = get_from_twitter_api end # View

    <% @tweets.each do |tweet| %> <%= tweet.author_name %>: <%= tweet.text %> <% end %> But the laziness stuff can actually be useful in your day to day work as a rails programmer. This is some fairly simple rails code to fetch some tweets from the twitter API and print them out.
  107. # Controller def index @tweets = Lazy.new { get_from_twitter_api }

    end # View <% @tweets.each do |tweet| %> <%= tweet.author_name %>: <%= tweet.text %> <% end %> But if you’re taking advantage of rails’s streaming response stuff, it’s essential to get the header of you page down to client as quickly as possible so the browser can start fetching the CSS and JS. If we make the request to twitter lazy,
  108. “I really wish we could use Java style annotations here.”

    “Tail-call recursive would make this code so much cleaner" So whenever you hear your coworker say “I really wish we could use Java style annotations here”,
  109. “I really wish we could use Java style annotations here.”

    “Tail-call recursion would make this code so much cleaner.” Or “Tail-call recursion would make this code so much cleaner”, realize that you can build these things into Ruby yourself.