Kestrels, Quirky Birds
Introduction
- Combinatory logic is used as a basis of functional programming languages.
- A combinator is a higher-order function that uses only function application and ealier defined combinators to define a result from its arguments.
[aside] CSE260 and CSE261 were all about Combinatory logic, Number theory, proving RSA algorithm from first principles.
So what will this book be about? We'll look at several combinators and their ramifications when writing programs using Ruby. Starting form K combinator and Ruby's .tap
method, to meta-programming, aspects and recursive combinators.
[word] ornithological - relating to the study of birds. "These ornithological nicknames have become part of the standard lexicon for combinatory logic."
S
andK
combinators express everything in Lambda Calculus and Set Theory.To Mock a Mockingbird
was a book that gave bird names to combinators for fun.
Kestrels
K Combinator is a function that returns a constant function. Kxy = x. In Ruby think about .tap
. Suppose we wanted to log the person, with we'd do this:
address = Person.find(...).tap { |p| log "person #{p} found }.address
Without using tap
, we'd need some temp variables to accomplish this logging. It'd be something awkward like this:
person = Person.find(...)
log "person #{person} found"
address = person.address
tap
is a method in all objects that passes self
to a block and returns self
. The result of the block is discarded, it is only there for side effects.
There is also a Krestel named returning
in Rails that this book mentions. (though this one is now deprecated). It illustrates the concept well. This method
def registered_person(params = {})
person = Person.new(params.merge(:registered => true)
Regisry.register(person)
person.send_email_notification
person
end
can be rewritten like this with returning
def registered_person(params = {})
returning Person.new(params.merge(:registered => true) do |person|
Registery.register(person)
person.send_email_notification
end
end
Object Initializer Blocks
The idea is to accept an optional block with new
and evaluate the block for side-effect. This pattern of wanting a Kestrel/returning/tap when you create a new object is so common that it's built into ActiveRecord
. So the above method can actually be written without the returning
.
def registered_person(params = {})
Person.new(params.merge(:registered => true)) do |person|
Registry.register(person)
person.send_email_notification
end
end
Inside, an Idiomatic Ruby Kestrel
For initializer block, it doesn't pass the new class to the block as a parameter necessarily but it evaluates the block in the context of the new class.
What does that mean specifically?
It means it evaluates the block with self
set to the new class. This is different from tap
and returning
. They leave self
untouched.
The Enchaining Kestrel
Object.tap
of Ruby 1.9 is also useful when we want to execute several methods on the same object without having to create a lot of temporary variables. Method chaining in other words. Something like HardDrive.new.capacity(150).external.speed(7200)
There is a really good example of using tap
for safely method chaining, with Array methods that don't always return the array, and sometime return nil
.
Example: .uniq!
apparently returns nil
if the array is already unique. (.uniq
on the other hand does return the array itself)
irb(main):009:0> arr = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):010:0> arr.uniq
=> [1, 2, 3, 4, 5]
irb(main):011:0> arr.uniq!
=> nil
So we can use .tap
to ensure we always get the array like this:
irb(main):012:0> arr.tap(&:uniq!)
=> [1, 2, 3, 4, 5]
And then we can method chain safely if needed [1,2,3,4,5].tap(&:uniq!).sort!
So we can use .tap
to enchain methods when the methods do not return the receiver.
The Obdurate Kestrel
This part is about an gem called andand
that adds Object#tap
to Ruby 1.8 as well another method called Object#dont
. #dont
simply ignores the block passed in and seems like it can be used for logging and debugging to NOOP a method call. It's kinda like commenting it out. An example is arr.dont.sort!
I don't like the idea of using a get to add behavior to an older version of Ruby. So, interesting but ignoring.
Kestrels on Rails
This section is comparing the semantics of returning
vs. object initializer blocks in Rails.
Returning
fully evaluates the expression, including all of its callbacks. The object initializer block, on the other hand, is called as part of initializing the object before starting the lifecycle of the object including its callbacks.
Returning
is what you want when you want to do stuff involving the fully created object, and logically group the other statements in the block together.
Object initializer is what you want when you want to initialize some fields by hand and perform some calculations before kicking off the object creation lifecycle.
Rewriting Returning
in Rails
I think returning
in Rails has been deprecated since Rails 3. But none the less, this section goes into all the reasons it's a good to use and also how it can be implemented.
First, there is a possibility of a subtle bug while using returning
. Namely, assignments inside the block to the variable being return have no effect and so the program does not do what may have been intended. Example:
returning ... do |var|
#...
var = something_else
#...
end
random - "The principle of least surprise" means the design should be internally consistent. Which is not the same thing as familiar.
There is a project called RewriteRails that added syntactic abstraction to Rails projects without monkey patching. This project is long deprecated of course. I checked and the last commit was in October 2012.
This RewritRails thing would change code involving returning
such that it would change the value of what was being returned to take into account assignments within the block. Interestingly, it would do this by rewriting the code you write so that the ruby interpreter sees that code and you see your code (apparently this is like Lisp macros). So for example, it would rewrite this:
def registered_person(params = {})
returning Person.new(params.merge(:registered => true)) do |person|
if Registry.register(person)
person.send_email_notification
else
person = Person.new(:default => true)
end
end
end
into this:
def registered_person(params = {})
lambda do |person|
if Registry.register(person)
person.send_email_notification
else
person = Person.new(:default => true)
end
person
end.call(Person.new(params.merge(:registered => true)))
end
The Thrush
In Combinatory logic, the thrush is a simple permuting combinator. It reverses evaluation. So Txy = yx.
With thrush called into
you can write this:
lambda { |x| x * x }.call((1..100).select(&:odd?).inject(&:+))
like this instead
(1..100).select(&:odd?).inject(&:+).into { |x| x * x }
The advantage being that the second one is clearer to read. Start with numbers from 1 to 100, take the odds ones, add them, then square them.
This type of permuting combinator is not strictly necessary when we have parentheses and local variables though.
Songs of the Cardinal
A Cardinal is a permuting combinator. So a Cardinal first passes a_proc to proc_over_proc and then passes a_value to the result. proc_over_proc is a function that takes a function and return a function.
Thrush can be thought of as a 'special' case of Cardinal where the proc_over_proc is an identity function (identity = lambda { |f| f }
)
What does all this mean? Let's see in Ruby code (the author mentions a limitation of define_method
in 1.8 that was no longer the case in 1.9. Since we are way passed Ruby 1.9 ignoring that part):
Here is a proc_over_proc:
do |a_proc|
lambda { |a_value|
a_proc.call(a_value) unless a_value.nil?
}
end
This takes a_proc
and returns a brand new proc that only calls a_proc
if the value passed is not nil.
An example of a Cardinal that is Thrush like:
cardinal_define(:let) do |a_proc|
a_proc
end
let( (1..10).select { |n| n % 2 == 1 }.inject { |mem, var| mem + var } ) do |x|
x * x
end
=> 625
This says take all of the odd numbers from 1-10 then add them together (so 1+3+5+7+9 = 25). That becomes the 'value' and the 'proc' is the block that squares the given value. So the final answer would be 25x25 or 625.
The whole point of all of this is that we can have a method that applies a value to a block but we can modify the semantics of the block in any way we want, on the fly. So this is, in-essence, meta-programming.
Quirky Birds and Meta-Syntactic Programming
First, what does metasyntax
mean from wikipedia:
In logic and computer science, a metasyntax describes the allowable structure and composition of phrases and sentences of a metalanguage, which is used to describe either a natural language or a computer programming language.
[aside] wikipedia mentions Backus-Naur form (BNF) and I recall learning about that along with a language call Prolog a while ago, vaguely
So the Quirky Bird combinator is like the Cardinal but while the Cardinal modifies the function that is applied to a value, Quirky Bird modifies the value itself. So the formal definition is:
def quirky_bird_define(a_value, &a_proc)
a_proc.call(value_proc.call(a_value))
end
value_proc
is the thing that changes the value before it's passed into a_proc
.
A concrete example:
quicky_bird_define(:square_first) do |a_value|
a_value * a_value
end
square_first(2) { |n| n+1 }
=> 5
Next, what if we wanted to define maybe
using Quirky Bird. With Cardinal we could writ a proc that would modify another proc to return nil if it was passed nil. With Quirky Bird this can't really be done in a way that feels natural There some awkward attempts.
In one attempt value_proc
will take a value, and if the value is nil
it will return an object that responds with nil
to any method called on it.
In another attempt, what if instead of writing maybe(nil) { |n| n+1 }
we do it like this nil.maybe + 1
or 1.maybe + 1
instead? In that case, maybe
becomes a method on the object class that applies value_proc
to its receiver. (this looks more method-oriented or object-oriented rather than function-oriented).
def quirky_bird_extend(method_name, &value_proc)
Object.send(:define_method, method_name) do
value_proc.call(self)
end
end
Copying this straight from the book, as it makes an important point:
We are using define_method and a block rather than the def keyword. The reason is that when we use define_method and a block, the body of the method executes in the context of the block, not the context of the object itself. Blocks are closures in Ruby, which means that the block has access to value_proc, the parameter from our quirky_bird_extend method.
Had we used def, Ruby would try to evaluate value_proc in the context of the object itself. So our parameter would be lost forever. Performance wonks and compiler junkies will be interested in this behaviour, as it has very serious implications for garbage collection and memory leaks.
That above definition of quirky_bird_extend then leads to the definition of maybe
and also of try
(! I love try, have found it very useful).
[aside] All of these examples are using BlankSlate
which is something I've never seen before. The idea is that it's an abstract base class with no predefined methods.
In summary, we can used the quirky bird to create a whole family of methods that modify the receiver in some way to produce new semantics.
Aspect-Oriented Programming in Ruby using Combinator Birds
Bluebird combinator introduced. It is special as it composes two other combinators. (think of the parens in (x * 2) + 1
)
It's written officially as
bludebird.call(proc1).call(proc2).call(value)
=> proc1.call(proc2.call(value))
The first line says proc1.call(proc2).call(value) meaning put proc2 into proc1 getting function back, then put value into that function.
The second line says put value into proc2, then put the result of that into proc1
[aside] I first encountered the term Aspect-Oriented Programming (AOP) in the Java programming language at work. I learned of terms like aspects and cross-cutting-concerns and advice. At the time, I understood the code that was doing this stuff in Java but of course don't recall any more. I associate before
and after
methods in Rails controller to this concept AOP. Also last thought, what a weird word aspect
to stick in front of 'oriented'
Don't see how the definitions so far connect to anything practical I know of AOP. Let's keep going.
Giving Methods Advice
This section confirms that calling things like after_save
, before_filter
are part of Rails support for aspect-oriented programming. then it tries to implement the idea of before methods in Ruby by using a 'trick' the author found on a blog.
At this point, things get into the handy-wavy territory. There is some code pasted in under the NativeBeforeMethods
module. With the paragraph after starting with 'as you can see,...' but not explaining anything about the code.
The example of using the before methods is pretty clear and straightforward. We all know this from experience. The before method gets called before the method we specified and does something useful/necessary for our intended method.
class SuperFoo
def one_parameter(x)
x + 1
end
def two_parameters(x, y)
x * y
end
end
class Foo < SuperFoo
include NaiveBeforeMethods
before :one_parameter do |x|
x * 2
end
before :two_parameters do |x, y|
[x + y, x - y]
end
end
Foo.new.one_parameter(5)
=> 11
Foo.new.two_parameters(3,1)
=> 8
The Super Keyword
The section makes the point that while we could use the super
keyword in places where we are using before method, doing so would be more low level and less clear.
By using 'advice' we can separate clearly what the method does from the other things we may need to decorate it with (e.g. logging). So it separates concerns.
The Queer Bird
This one does things in the opposite direction and useful for after methods. The difference between before and after advice is that after advice is consumes and transforms whatever the method returns, while before advice consumes and transforms the parameters to the method.
The rest of this section is pretty hand-wavy, not actually teaching/explaining anything.
[todo] write about instance_eval
and instance_exec
for codecuriou.dev
Mockingbirds
The Mockingbird combinator is used to achieve recursion. It's a combinator that does not conserve it's arguments. It changes them by duplicating Mx = xx
.
Recursive Lambdas in Ruby
With the below example of summing numbers using a recursive lambda, we create a need to have lambdas call themselves without using their names. We do this by figuring out how to make all kinds of lambdas anonymous and flexible.
sum_of_nested_list = lambda do |arg|
arg.kind_of?(Numeric) ? arg : arg.map { |item| sum_of_nested_list.call(item) }.inject(&:+)
end
(This is not the 'natural' way to write this recursion - it calls the lambda again for every single number one by one - but I guess it's done this way for sake of making a point about why the lambda needs to be anonymous)
Section 7.3 The progressive code snippets to make an anonymous lambda that call itself are...the motivation for needing to do this is poorly explained. The why question is not answered sufficiently. Making the whole read a bit unsatisfying.
[1, 2, 3].inject(&:+) == 6
:+.to_proc.call(1, 2)
String to Proc
The #to_proc
method of the String
class allows us to write certain simple lambdas as strings instead of using the lambda
keyword, the proc
keyword, or Proc.new
. This gets rid of the noise.
Aside: to demonstrate the noise, and motivate and use of String#to_proc
the author using the analogy that having the keyword lambda
everywhere in the definition of recursive algorithms would like if at a poetry reading the author shouts the punctuation, as in
Two roads diverged in a yellow wood COMMA!
And sorry I could not travel both
And be one traveler COMMA! long I stood
And looked down one as far as I could
To where it bent in the undergrowth SEMI-COMMA!!
This made me laugh out loud. Also reminded me of reading this poem in high school. A road less traveled indeed.
Anyway, back to lambda
and String#to_proc
. The idea is that we can take this lambda { |x,y| x + y }
and write it as 'x,y -> x + y'
.
The ->
is keeping with modern functional languages while lambda
is in keeping with Lisp and lambda calculus. The ->
also resembles arrow functions in ES6+.
Inferred parameters
This means that if we write x + y
, String#to_proc
treats it as x,y -> x + y
. This works for simple cases, don't get too fancy with it.
"it"/the hole
If there is only one parameter, we can use _
(underscore) without naming it. So this code
multirec(
'x.kind_of?(Numeric)',
'x ** 2',
'x',
'z -> z.inject { |sum, n| sum + n }'
)
would become this
multirec(
'_.kind_of?(Numeric)',
'_ ** 2',
'_',
'_.inject { |sum, n| sum + n }'
)
The author says using the "it" is a matter of taste. And yeah. I do not like it. Looks unpleasant and unclear.
point-free
This terminology I have never heard before. "function points" is what functional programmers usually call parameters. Point-free style is about describing how functions are composed together rather than about describing what happens to their arguments.
Example! With String#to_proce
magic this
lambda { |z| z.inject { |sum, n| sum + n } }
can be written as
".inject(&'+')"
That's exactly how (1..100).inject(&:+)
in rails works.
The author offers some rules of thumb about when to use all these options provided by String#to_proc
and when not to. Most of those rules sensible, though don't result in something that looks clear to me or more elegant (than being more explicit).
There is an example at the bottom of two variations of a function that rotates a matrix. The first is suppose to be hard to read/debug/modify as the fact that it's using recursion/divide-n-conquer is 'hidden'. The second variation makes this more explicit using lambdas and String#to_proc
.
Neither seem that clear to me. But this point resonates, the goal is 'to disentangle the question of what we are doing from how we are doing it'. And the option to make the ceremonial parts of the code go away and emphasize the part that matter is nice (unlike other languages where the ceremony is much longer the 'meat' of the function and does feel like the poet shouting out punctuations).
Object-oriented egocentricity
So in OO programming is that programs consist of objects that respond to messages they send each other. A hopelessly egocentric object is easy to imagine: No matter what message you send it, the hopelessly egocentric object responds with itself.
Imagine making nil
egocentric. So when we send a message to nil
we get back nil
. Instead of NoMethodError
. That seems desirable for something like person.name.upcase
so we don't have to check for nil
.
But it's not as simple as that. Making nil
egocentric makes sense for queries but not for updates, where the message we send is suppose to have an side effect like person.account.increment_balance(100)
. It doesn't make any sense for that to return nil
or fail silently.
The author lists a number of considerations based on whether the meaning of nil
in our context is NONE or UNKNOWN. And whether we are doing a purely functional transformation with a query or if we're doing updates with side effects.
Checking equality with UNKNOWN and truthiness get tricky also.
All of the nuances enumerated in this chapter lead to the conclusion that, while the idea is appealing, implementing an hopelessly egocentric nil
in ruby is a no-go. We go with explicit idioms like #try
and think deeply about the semantics of our data schemas.