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