Slide 1

Slide 1 text

Extending Ruby with Ruby

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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.

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

We automatically detect the edges using computer vision.

Slide 7

Slide 7 text

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.

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Ruby Ruby.

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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.

Slide 13

Slide 13 text

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.

Slide 14

Slide 14 text

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.

Slide 15

Slide 15 text

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.

Slide 16

Slide 16 text

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.

Slide 17

Slide 17 text

Python Let’s talk about Python.

Slide 18

Slide 18 text

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.

Slide 19

Slide 19 text

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?

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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.

Slide 24

Slide 24 text

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.

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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.

Slide 27

Slide 27 text

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.

Slide 28

Slide 28 text

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.

Slide 29

Slide 29 text

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.

Slide 30

Slide 30 text

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.

Slide 31

Slide 31 text

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.

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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.

Slide 35

Slide 35 text

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.

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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.

Slide 38

Slide 38 text

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.

Slide 39

Slide 39 text

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.

Slide 40

Slide 40 text

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.

Slide 41

Slide 41 text

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.

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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.

Slide 44

Slide 44 text

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.

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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,

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Diving in

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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.

Slide 54

Slide 54 text

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.

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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.

Slide 57

Slide 57 text

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.

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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.

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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.

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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.

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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.

Slide 68

Slide 68 text

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.

Slide 69

Slide 69 text

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.

Slide 70

Slide 70 text

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.

Slide 71

Slide 71 text

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.

Slide 72

Slide 72 text

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.

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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.

Slide 75

Slide 75 text

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.

Slide 76

Slide 76 text

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.

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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.

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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.

Slide 82

Slide 82 text

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.

Slide 83

Slide 83 text

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,

Slide 84

Slide 84 text

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.

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

[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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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?

Slide 94

Slide 94 text

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.

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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.

Slide 97

Slide 97 text

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?

Slide 98

Slide 98 text

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.

Slide 99

Slide 99 text

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.

Slide 100

Slide 100 text

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.

Slide 101

Slide 101 text

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.

Slide 102

Slide 102 text

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.

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

Haskell

Slide 111

Slide 111 text

Lazy evaluation Example time:

Slide 112

Slide 112 text

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.

Slide 113

Slide 113 text

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.

Slide 114

Slide 114 text

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”

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

$ ./rails_conf So, we run this program

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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.

Slide 122

Slide 122 text

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.

Slide 123

Slide 123 text

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.

Slide 124

Slide 124 text

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.

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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.

Slide 128

Slide 128 text

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.

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

Coda

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

Michael Fairley 1000memories github.com/michaelfairley @michaelfairley