Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. Extending Ruby
    with Ruby

    View Slide

  2. Michael Fairley
    @michaelfairley
    github.com/michaelfairley
    My name is Michael Fairley, and I’m on Twitter and GitHub very cleverly as michaelfairley.

    View Slide

  3. 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.

    View Slide

  4. We have an iPhone app called ShoeBox that makes it really it really to scan old paper photos.

    View Slide

  5. You take a picture of an old paper photograph. (This is me on my first birthday)

    View Slide

  6. We automatically detect the edges using computer vision.

    View Slide

  7. 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.

    View Slide

  8. 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...

    View Slide

  9. Ruby
    Ruby.

    View Slide

  10. And my great-grandfather who I called Papaw, his name –

    View Slide

  11. Singleton
    Singleton. It’s almost like being a programmer was my destiny.

    View Slide

  12. 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.

    View Slide

  13. 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.

    View Slide

  14. 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.

    View Slide

  15. 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.

    View Slide

  16. 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.

    View Slide

  17. Python
    Let’s talk about Python.

    View Slide

  18. 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.

    View Slide

  19. 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?

    View Slide

  20. 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...

    View Slide

  21. 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...

    View Slide

  22. 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).

    View Slide

  23. 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.

    View Slide

  24. 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.

    View Slide

  25. 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...

    View Slide

  26. 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.

    View Slide

  27. 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.

    View Slide

  28. 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.

    View Slide

  29. 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 here’s what that `transactional` function might look like.

    View Slide

  30. 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.

    View Slide

  31. 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 defines a new function.

    View Slide

  32. 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

    View Slide

  33. 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

    View Slide

  34. 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 finally it returns the newly defined function.

    View Slide

  35. 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.

    View Slide

  36. @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.

    View Slide

  37. 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.

    View Slide

  38. 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.

    View Slide

  39. 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.

    View Slide

  40. 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.

    View Slide

  41. 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.

    View Slide

  42. 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...

    View Slide

  43. 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.

    View Slide

  44. 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.

    View Slide

  45. 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

    View Slide

  46. 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,

    View Slide

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

    View Slide

  48. @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,

    View Slide

  49. @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.

    View Slide

  50. +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.

    View Slide

  51. Diving in

    View Slide

  52. +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.

    View Slide

  53. 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.

    View Slide

  54. 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.

    View Slide

  55. class Transactional < Decorator
    def call(orig, *args, &blk)
    ActiveRecord::Base.transaction do
    orig.call(*args, &blk)
    end
    end
    end
    We open a transaction.

    View Slide

  56. 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.

    View Slide

  57. 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.

    View Slide

  58. 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

    View Slide

  59. 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

    View Slide

  60. 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.

    View Slide

  61. 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

    View Slide

  62. 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.

    View Slide

  63. 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

    View Slide

  64. 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.

    View Slide

  65. 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

    View Slide

  66. 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

    View Slide

  67. 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.

    View Slide

  68. 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.

    View Slide

  69. 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.

    View Slide

  70. 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.

    View Slide

  71. 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.

    View Slide

  72. 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.

    View Slide

  73. 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

    View Slide

  74. 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.

    View Slide

  75. 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.

    View Slide

  76. 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.

    View Slide

  77. Scala
    Let’s move on to talking about Scala for a bit.

    View Slide

  78. 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.

    View Slide

  79. [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...

    View Slide

  80. [1,2,3].map(&:to_s)
    this. You’ve all seen this.

    View Slide

  81. 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.

    View Slide

  82. 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.

    View Slide

  83. 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,

    View Slide

  84. 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.

    View Slide

  85. [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...

    View Slide

  86. [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.

    View Slide

  87. [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.

    View Slide

  88. [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.

    View Slide

  89. [1,2,3].map(&_{ _ + 1 })
    What’s happening here is that we’re passing a block to a method called

    View Slide

  90. [1,2,3].map(&_{ _ + 1 })
    `_`

    View Slide

  91. [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

    View Slide

  92. [1,2,3].map(&_{ _ + 1 })
    to evaluate this block in an interesting way.

    View Slide

  93. 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?

    View Slide

  94. 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.

    View Slide

  95. 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

    View Slide

  96. 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.

    View Slide

  97. 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?

    View Slide

  98. 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.

    View Slide

  99. 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.

    View Slide

  100. 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.

    View Slide

  101. 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.

    View Slide

  102. 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.

    View Slide

  103. [1, 2, 3].map(&_{ _ + rand })
    So let’s walk through this again.

    View Slide

  104. [1, 2, 3].map(&_{ _ + rand })
    This `_` generates a new instance of PartialApplication that is initialized with the block.

    View Slide

  105. [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.

    View Slide

  106. [1, 2, 3].map(&_{ _ + rand })
    That lambda instance evals the block on that instance of Execution.

    View Slide

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

    View Slide

  108. [1, 2, 3].map(&_{ _ + rand })
    And when rand is called, it’s evaluated in this scope, returning a random number.

    View Slide

  109. [1, 2, 3].map(&_{ _ + rand })
    => [1.80115404, 2.99946462, 3.86742826]

    View Slide

  110. Haskell

    View Slide

  111. Lazy evaluation
    Example time:

    View Slide

  112. 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.

    View Slide

  113. 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.

    View Slide

  114. 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”

    View Slide

  115. 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

    View Slide

  116. 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

    View Slide

  117. $ ./rails_conf
    So, we run this program

    View Slide

  118. $ ./rails_conf
    I’m lazy
    and out pops “I’m lazy” as expected

    View Slide

  119. $ ./rails_conf
    And then I turned off my wi-fi and ran it again.

    View Slide

  120. $ ./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.

    View Slide

  121. 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.

    View Slide

  122. 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.

    View Slide

  123. 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.

    View Slide

  124. 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.

    View Slide

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

    View Slide

  126. +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.

    View Slide

  127. 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.

    View Slide

  128. 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.

    View Slide

  129. # 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.

    View Slide

  130. # 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,

    View Slide

  131. Coda

    View Slide

  132. “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”,

    View Slide

  133. “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.

    View Slide

  134. Michael Fairley
    1000memories
    github.com/michaelfairley
    @michaelfairley

    View Slide