Yuri Leikind
A higher-order function is a function that does at least one of the following:
Usual thing for every Javascript developer:
All of these use higher-order functions as the primary construct.
If fact, functions as arguments are so ubiquitous that there is a special
syntax for it.
A block is a higher-order function which is an argument to a method call
Standard library:
Sinatra
Rails routes:
Rspec:
A Rails plugin by yours truly:
You can read more on Ruby blocks and how they are special in a post by Yehuda Katz The Building Blocks of Ruby
http://yehudakatz.com/2010/02/07/the-building-blocks-of-ruby/
when you can build language constructs with blocks and while
:
Using only each
Ruby can implement the same set of higher-order methods on enumerables as functional languages:
map
reject
(filterNot
in other languages)select
(filter
in other languages)take
flat_map
all?
reduce
All these are found in module Enumerable which can be included in any collection class which implements each
Example implementation:
Example implementation:
Example implementation:
Each such method creates a new collection. They can be huge…
We can rewrite this code to process each word one by one. That’s one solution…
This is what we have, a sequential execution:
Our blocks working concurrently, and processing elements one by one:
This is what we have
|
This is what we need
|
The second modus operandi is on demand, it is lazy.
Fibers were added in Ruby 1.9
Here’s what the documentation says:
Fibers are primitives for implementing light weight cooperative concurrency in Ruby. Basically they are a means of creating code blocks that can be paused and resumed, much like threads. The main difference is that they are never preempted and that the scheduling must be done by the programmer and not the VM.
The goal of this implementation is only to demonstrate how laziness can be achieved with the help of fibers.
This is neither a complete nor a true implementation of Enumerable::Lazy
in Ruby 2.0.
Beauty and completeness have been sacrificed in favor of simplicity.
Enumerable::Lazy
in Ruby 2.0 uses Enumerator
added in Ruby 1.9 as the basis.
Enumerator
uses fibers. This proof of concept code uses fibers directly because
Enumerator
is beyond the scope of this presentation.
Let’s create a wrapper class which will contain a fiber (which in its turn will contain the block defined in the client code)
Let’s add a method in Enumerable
which will create an instance of this wrapper out of an enumerable:
The wrapper object should have an API similar to Enumerable
. each
should be non-lazy, map
, select
and others should be lazy.
Let’s implement the non-lazy behavior and each
. We just access the underlying enumerable via a fiber, no added logic here:
Oops…
The value of the block is also returned from the fiber, so we have to rewrite each
to ignore the last value returned from the fiber.
Let’s implement map
and select
. We’ll convert the client code block to an explicit function object for the sake of readability.
We are incapsulating the client code block in yet another function which implements the logic of map
or select
, but it calls
Fiber.yield
to return the result, because this function will be called from within a fiber.
Both map
and select
return a new LazyPanda
instance incapsulating new logic. In a way, we are dealing with a Russian doll pattern: the first
LazyPanda
incapsulates a real Enumerable, the next LazyPanda
incapsulates the first LazyPanda
, and so on.
Now we need to modify the constructor. The constructor should have a second optional function parameter. In case such a function is present, the constructor places this function into a fiber instead of the default Fiber.yield(e)
:
No output. It is lazy. This code has only created the Russian doll structure. No block has been executed.
Voila!