Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / Ruby

Function Composition, Function Chaining, Currying, and Partial Functions / Application in F# and Ruby

Rate me:
Please Sign up or sign in to vote.
4.95/5 (4 votes)
14 Oct 2013CPOL10 min read 24.4K   7   1
Exploring how to implement functional programming features such as function composition and chaining in Ruby.

Introduction

A while ago I wrote an article comparing / contrasting language elements in Ruby with C# with regards to classes: constructors, destructors, fields, properties, initializers, events, methods, etc.  Ruby however also has a foot in the functional language paradigm, though it requires a certain discipline to ensure that you're adhering to qualities of a functional programming language such as statelessness and immutability.  However, for purposes of this article, what I am more interested in is exploring how Ruby handles certain concepts that seem to be core to most functional programming languages:

  • function composition
  • function pipelining (chaining)
  • continuation functions
  • currying
  • partial functions
  • partial application

These concepts are all intertwined with each other, and as we'll see, you can certainly implement these behaviors in Ruby, though not as elegantly as with F# (or other FP languages.)  Incidentally, one of the reasons Ruby gets away with being called an FP language is its support for lambda expressions and the ease in which code blocks yielded to for iteration, application specific handling (like IoC), and so forth.  Again, those are not areas that I'm interested in pursuing in this article because at this point they are part of most imperative languages as well, such as C#, further blurring the lines between imperative, declarative, and functional programming.

Background

The impetus for this investigation was a little helper function that I ended up writing a couple days ago.  This is a bit of a diversion, so sit back and enjoy the brief detour.  One of the interesting things about Ruby on Rails development is the ability to write "features" that define "scenarios" comprised of "steps" that test the behavior of the website.  This is all part of the Behavior Driven Development paradigm many Ruby developers adhere to, and this capability is provided by a package called Cucumber, which also works with .NET.  Other BDD testing paradigms in the .NET world are SpecFlow and NSpec.

Like anything that is intended to be helpful, it results in its own set of problems.  For BDD, this becomes the large number of steps that an application defines, often across numerous files.  Steps look like this:

When /^(?:|I )go to (.+)$/ do |page_name|
  visit path_to(page_name)
end

or:

When /^(?:|I )fill in "([^"]*)" for "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
  with_scope(selector) do
    fill_in(field, :with => value)
  end
end

What's a developer to do to keep track of it all, and how can we document what all the different parameters are?  Well, there's a nifty way for Ruby to inspect itself and so, borrowing from the work of others, I wrote a small set of functions to sort all the BDD testing steps and display them as an HTML document.  Here's a snippet of what it looks like in its output:

Image 1

You can see all the Ruby code on my blog, but the salient point is this code:

def step_definitions_to_html
  # Where are your step definitions defined?
  step_definition_dir = "./features/step_definitions"
  # Where do you want output to go:
  outfile = "output.htm"

  steps = get_step_definitions(step_definition_dir)   # get the step definitions
  steps = sort_steps_by_name(step)	              # sort them
  output_step_definitions_as_html(outfile, steps)     # output as HTML
  show_html(outfile)                                  # show the file in the browser
end

which in my opinion is horribly imperative.  I thought it would be interesting to write this as a function composition or function pipeline, looking, abstractly, like this:

get_steps(input_path) >> sort_steps >> display_steps(output_file)

What I didn't realize was the number of rabbit holes I would have to go down with Ruby for something that ultimately is very simple in F#.

Function Pipelining

Let's make the problem a little simpler.  Let's say:

I have a function "a" that converts an integer to a string.

F#:

let a n = Convert.ToString(n:int32);;
val a : n:int32 -> string

Read this as: a is a function that takes an int32 and returns a string.

Ruby:

def a(n) n.to_s; end

I have a function "b" that appends a fixed string with two parameters

F#:

let b n intstr = Convert.ToString(n:float) + ", " + intstr;;
val b : n:float -> intstr:string -> string

Read this as: b is a function that takes a float and a string and returns a string.

Ruby:

def b(n, instr) n.to_s + ", " + instr; end

The reason we're doing this with different types is because it's easier to see what the intermediate functions are when they have different types - we'll be looking at function pipelining and composition with the types int, string, and float.

One of those two parameters is the output of function "a", which converts an integer to a string, such that I can write the function chain:

F#:

3 |> a |> b 1.2;;
val it : string = "1.2, 3"

Given the function b that we defined above:

let b n intstr = Convert.ToString(n:float) + ", " + intstr

We can see that the function a evaluates to "3".  This becomes the input to the last parameter of function b, resulting in a curried function in which b's first parameter (n) still needs to be provided:

let b n intstr = Convert.ToString(n:float) + ", " + "3"

However, because this is function pipelining, we can't create an intermediate function (which would be function composition).  So, for example, this results in an error:

 let r = 3 |> a |> b;;
------------------^

stdin(146,19): error FS0001: Type mismatch. Expecting a
string -> 'a 
but given a
float -> string -> string 
The type 'string' does not match the type 'float'

So, with function pipelining, we need to provide all the parameters so that the entire pipeline can be evaluated.

How do we do this in Ruby?  The problem with the word "pipelined" is that some people in Ruby-land interpret it as running in a separate thread (or fiber), not waiting for the function to continue before executing the next function in the pipe.  This isn't what we want.  Then, the problem with the word "chaining" is that almost everyone in Ruby-land interprets it as function that return "self" (the equivalent to "this" in C#) so that another function of the object can then be called, resulting in notation like:

a(3).b("def")

which of course isn't what we want either because a(3) would returns an object, not "3"!  What we want to do is preserve the original meaning of the function rather than having to coerce a particular coding style.

Now, going back to the F# code, the pipeline operator is defined to simply swap the function and the argument:

let inline (|>) x f = f x

To do something like this in Ruby, we have to go down the rabbit holes of lambda expressions, the Proc class, and for syntax-sugar purposes, operator overrides.

Extending Ruby With Functions

Function pipelining (and as we'll see later, function composition) are not built-in behaviors of the Ruby language.  However, we can create functions that behave this way by converting our Ruby functions and values into lambda expressions.  This allows us to define operators on the expression as well as evaluate it.  So, first, let's define a lambda that simply returns the value 3:

l3 = lambda {3}
=> #<Proc:0x2a12f60@(irb):33 (lambda)>

As you can see, this return a Proc object.  We can evaluate the expression (note the syntax, there's three different ways of evaluating lambda expressions!):

l3.()
=> 3

And we see that it returns 3.

Now we need a lambda that encapsulates our function a, which converts numbers to the strings:

la = lambda{|x| a(x)}
=> #<Proc:0x2a1e480@(irb):43 (lambda)>

And if we evaluate this:

la.(3)
=> "3"

We get the expected string conversion.

Now we just need to do the equivalent of:

la.(l3.())
=> "3"

using an operator to get the syntactical sugar we want.  This involves overriding the operator:

class Proc
  def self.chain(f, g)
    lambda { g[f.()] }
  end
  def >=(g)
    Proc.chain(self, g)
  end
end

Here, the >= operator is overloaded (Ruby only allows overloading of operators that are already defined), such that the chain function is called with two parameters, the left operand and the right operand.  The function chain returns a lambda expression that evaluates the right operand, passing in as a parameter the evaluated lambda expression of the left operand.  Thus we can see the equivalent of the F# implementation:

let inline (|>) x f = f x

Now, when we enter into our Interactive Ruby Console (IRB):

(lambda{3} >= lambda{|n| n.to_s}).()

this evaluates to the string "3".

Now, this is a lot of typing and looks ugly as sin, and remember, we want this to work with existing Ruby functions, such as those we had defined earlier:

def a(n) n.to_s; end
def b(n, instr) n.to_s + ", " + instr; end

So we'll create a couple helper functions:

def val(n) lambda {n}; end
def fnc(f) lambda(&method(f)); end

The function val takes a value and convert it into a lambda expression.  The function fnc, given a function, converts the function into a lambda expression.  We can now write:

(val(3) >= fnc(:a)).()
=> "3"

Note how we "symbolize" function a.

But where do we put the parameter value "1.2" if we want to chain in function b?  The answer lies in our exploration of the F# code above and going down another Ruby rabbit hole, currying.

Currying

The next rabbit hole on our journey into Ruby-in-Functional-land is to realize that, because function b has two parameters, what we need to do is curry the function, passing in the value from the left side of the >= operator and leaving the rest of the parameters to be specified by the caller.  We can do this explicitly:

(val(3) >= fnc(:a) >= fnc(:b).curry.("1.2")).()
=> "1.2, 3"

Thus, the string "3" will be passed in as the parameter intstr, leaving the curried function simply as:

def b(n, instr) n.to_s + ", " + "3"; end

We can make this a little cleaner with a currying "make function into a lambda expression" function:

def fncc(f, *args) lambda(&method(f)).curry.(*args); end

And express the function chain thus:

(val(3) >= fnc(:a) >= fncc(:b, 1.2)).()
=> "1.2, 3"

Behind the scenes, when fnc(:a) >= fncc(:b) evaluates, the value to the left of the operator is passed in to the function b as the last parameter, and the fncc returns a partial function (as a lambda) that takes the remaining (first) parameter.

So.  We have accomplished function chaining a la F#, though it's rather awkward because we have to be explicit about the currying (as well as converting functions to lambda expressions), whereas the equivalent in F#:

3 |> a |> b 1.2;;
val it : string = "1.2, 3"

is much cleaner. 

If Only...

Note that if we didn't have to deal with those pesky additional parameters in the function b, we could write:

(val(3) >= :a).()

if we were to change the >= operator definition to perform the "lambda-ization" for us:

class Proc
  def self.chain(f, g)
    lambda { g.(f.()) }
  end
  def >=(g)
    Proc.chain(self, fnc(g))
  end
end

Unfortunately, doing this functions that require additional paramerters just doesn't work.

Function Composition

Truth be told, what we've really done is actually more like function composition because we've been turning even values, like 3, into lambda's.  However, function composition behaves a bit differently in F#, and, leveraging the work we've already done, we can implement function composition in Ruby similarly.

First off, in F#, we can't do this:

 3 >> a >> b 1.2;;
 ^

stdin(92,1): error FS0001: This expression was expected to have type
'a -> 'b 
but here has type
int 

Function composition expects a function, not a int.

But we can do this:

(a >> b 1.2) 3;;
val it : string = "1.2, 3"

Here we have created a composed partial function:

a >> b 1.2;;
val it : (int32 -> string) = <fun:it@152-28>

The composite function is expecting an integer and returns a string.  It's a partial application because we've already applied the last parameter to the function b.

Note again how F# has done the currying implicitly, except now we can assign the composite function:

let r = a >> b 1.2;;
val r : (int32 -> string)

> r 3;;
val it : string = "1.2, 3"

Remember how in F#, the pipeline operator simply switches the parameters:

let inline (|>) x f = f x

Well, with function composition, the >> composition operator does the same thing:

let inline (>>) f g x = g(f x)

Here, reversing f and g, such that g is called with the result of the computation of "f x".

We can define the same operator and behavior in Ruby:

class Proc
  def self.compose(f, g)
    lambda { |*args| g[f[*args]] }
  end
  def >>(g)
    Proc.compose(self, g)
  end
end

and, having created a few helper functions earlier, we can now compose functions (notice the explicit currying in the fncc function):

(fnc(:a) >> fncc(:b, 1.2)).(3)
=> "1.2, 3"

Which compare this to the F# syntax above:

(a >> b 1.2) 3;;

Summarizing What We've Done So Far

F# Function Pipelining

let a n = Convert.ToString(n:int32);;
let b n intstr = Convert.ToString(n:float) + ", " + intstr;;
3 |> a |> b 1.2;;

Ruby Function Pipelining

class Proc
  def self.chain(f, g)
    lambda { g[f.()] }
  end
  def >=(g)
    Proc.chain(self, g)
  end
end

def val(n) lambda {n}; end
def fnc(f) lambda(&method(f)); end
def fncc(f, *args) lambda(&method(f)).curry.(*args); end

def a(n) n.to_s; end
def b(n, instr) n.to_s + ", " + instr; end

(val(3) >= fnc(:a) >= fncc(:b, 1.2)).()

F# Function Composition

let a n = Convert.ToString(n:int32);;
let b n intstr = Convert.ToString(n:float) + ", " + intstr;;
(a >> b 1.2) 3;;
let r = a >> b 1.2;;
r 3;;

Ruby Function Composition

class Proc
  def self.compose(f, g)
    lambda { |*args| g[f[*args]] }
  end
  def >>(g)
    Proc.compose(self, g)
  end
end

def val(n) lambda {n}; end
def fnc(f) lambda(&method(f)); end
def fncc(f, *args) lambda(&method(f)).curry.(*args); end

def a(n) n.to_s; end
def b(n, instr) n.to_s + ", " + instr; end
(fnc(:a) >> fncc(:b, 1.2)).(3)
r = (fnc(:a) >> fncc(:b, 1.2))
r.(3)

An Alternative Implementation for Function Pipelining

Jörg W Mittag answer my stackoverflow question regarding an equivalent to F#'s function chaining with a different implementation not involving operator overloading:

class Object
  def pipe(callable)
    callable.(self)
  end
end

Notice that the pipe method is being defined in the Object class, and also notice the idiomatic "->" syntax to define a lambda.

We can use this code like this (leveraging our other function helpers):

3.pipe(fnc(:a)).pipe(fncc(:b, 1.2))
=> "1.2, 3"

Notice how we don't need to convert the literal value 3 into a lambda because we're extending the Object class, and everything in Ruby is an object, thus, "pipe" becomes a function that can be applied to the number 3.  The lambda function is passed in and then evaluated, passing the object that called pipe.  This effectively implements "x.pipe(f) = f(x)".  A very snazzy piece of code!

Where Are We Now?

Remember that Ruby code I wanted to change into a more functional programming style implementation?

def step_definitions_to_html
  # Where are your step definitions defined?
  step_definition_dir = "./features/step_definitions"
  # Where do you want output to go:
  outfile = "output.htm"
  steps = get_step_definitions(step_definition_dir)   # get the step definitions
  steps = steps.sort_by {|step| step.regex}           # sort them
  output_step_definitions_as_html(outfile, steps)     # output as HTML
  show_html(outfile)                                  # show the file in the browser
end

It would now look like this:

def step_definitions_to_html
  step_definition_dir = "./features/step_definitions"
  # Where do you want output to go:
  outfile = "output.htm"

  steps = val((step_definition_dir) >=
          fnc(:get_step_definitions) >=                        # get the step definitions
          fnc(:sort_steps_by_name) >=	                       # sort them
          fncc(:output_step_definitions_as_html, outfile) >=   # output as HTML
          fnc(:show_html)                                      # show the file in the browser
end

Conclusion

At the end of the day, I probably wouldn't even write this code using pipelining in F#, but rather resort to the more "imperative-ish" style of using local variables to store the intermediate results.  None-the-less, this has been a interesting journey into some of the more esoteric aspects of the Ruby language.

Acknowledgements and References

Jörg W Mittag on stackoverflow for his excellent answer suggesting an alternative implementation for function pipelining
Ruby Functional Programming
Composing Functions in Ruby
Functions (F#)
The Ruby Language, Operator Expressions
Understanding Ruby Blocks, Procs, and Lambdas 
Ruby Currying

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
QuestionFunction chaining in C# Pin
HoCho15-Oct-13 19:32
HoCho15-Oct-13 19:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.