Bring a bit of Clojure/Racket to Ruby!
I made a new gem based on some exploration I did, but to tell the story, let’s talk about a possible use case.
Let’s say you’re writing some Ruby code where you need to call a sequence of methods on some initial value. (This is a contrived example, so bear with me)
class FileChecker
def report(file)
# need to produce summary after running checks
end
# add error strings to an instance variable if check fails
def check_columns(input); end
def check_blanks(input); end
def check_constraints(input); end
# selects error strings and other info to return
def summarize(input); end
end
There are probably several ways to approach this. You could, for instance, make the input
in those check
methods and that summary
method not an argument but an instance variable that is implicitly provided, allowing you to do this in #report
:
# along with changes to all those check methods and to summarize to not take arguments...
def report(file)
@contents = File.read(file)
check_columns
check_blanks
check_constraints
summarize
end
This could be a totally workable option, but what if you could enhance the testability and make the arguments more explicit for each helper method without using a bunch of tedious assignments to local variables in #report
? What if you could get the benefits of working with ActiveRecord::Relation
, where you can just chain where
and joins
and select
and so on all in a row?
I experimented with this idea in the newly released pipeable
gem. Here’s what you could do with that gem:
require 'pipeable'
class FileChecker
include Pipeable
def report(file)
contents = File.read(file)
output =
Pipeable(contents) |
:check_columns |
:check_blanks |
:check_constraints |
:summarize
output.value
end
...
end
Now, you can write pretty simple specs for those check
methods; just pass the appropriate value in (no need to check if it’s a Pipeable or to ‘unwrap’ it at all, just use the value directly). It allows you to arbitrarily break down functionality into smaller and smaller methods without losing out on composability. There’s a reason ->
in Clojure pops up everywhere (more on this later)!
Basically, you can wrap any value and then ‘pipe’ it through methods or stabby lambdas or Procs or even instances of Method. At the end, you can see the value by calling .value
on the result.
Another benefit: if your methods take and return Pipeable objects, you can further streamline your API and easily chain private methods or switch them around as needed.
This gem is ~34 lines of code and has 0 dependencies, and it doesn’t muffle errors. Also, it doesn’t monkey patch or use any meta programming. So, it should be fairly easy to ensure correctness of the behavior.
Here’s the gem on github! Check it out, fork, do whatever! It’s under a permissive MIT license.
Similar Stuff That Exists in Other Languages
Clojure has this useful macro called the ‘thread’ macro. Here’s an example:
(-> 1 (+ 1) (* 2))
; evaluates to `4`
So, ->
takes the first argument, 1
, and ‘threads’ it into the (+ 1)
form as the first argument, and so on for all other forms that follow. Here’s a doc with more examples.
Racket has a similar threading macro called ~>
, and here’s docs about it.