Juxtaposition in Ruby

4Clojure recently introduced me to Clojure’s curious juxt function. From the official docs:

Takes a set of functions and returns a function that is the juxtaposition of those functions.

Makes a bit more sense once you see it in practice:

1
2
((juxt + max min) 2 3 5 1 6 4)
; => [21 6 1]

This alone is pretty neat, but it gets better. Jay Fields observed that juxt has other interesting applications:

1
2
((juxt filter remove) even? [1 2 4 3 5 6])
; => [(2 4 6) (1 3 5)]

Implementing juxtapose in Ruby sounds like my idea of a good time. How might we go about this? We immediately run into a problem if we want to mimic the interface of Clojure’s juxt: The first example takes advantage of variadic functions that lack Ruby equivalents. Examining one of these functions, however, gives us a pretty strong hint:

1
2
(+ 1 2 3 4)
; => 10

This looks like a reduce function! Reduce is the most universal of functional programming’s three cornerstone functions (map, reduce and filter), so some sort of reduce-based solution should provide the desired results. In our first example, we pass three functions and get three results back, a pretty strong hint we should map our list of functions over the reduce operation. Our first implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
juxt = ->(*fns) do
  ->(*args) do
    fns.map do |fn|
      args.reduce {|acc, e| fn.to_proc.call acc, e}
    end
  end
end

max = ->(a, b) {a > b ? a : b}
min = ->(a, b) {a < b ? a : b}

juxt.(:+, max, min).(2, 3, 5, 1, 6, 4)
# => [21, 6, 1]

filter = ->(fn, list) {list.select {|e| fn.to_proc.call e}}
remove = ->(fn, list) {list.select {|e| !fn.to_proc.call e}}

juxt.(filter, remove).(:even?, [1, 2, 3, 4, 5, 6])
# => [[2, 4, 6], [1, 3, 5]]

Jackpot! This solution works and we even maintained the variadic interface from Clojure. The downside is that this solution requires us to recreate most of the functionality provided by Enumerable and Array. While I rarely turn down the opportunity to write a stabby lambda, let’s assume we want to avoid this for the sake of argument. We can send symbols to our list of arguments if we are willing to adapt the interface. One way to go about it:

1
2
3
4
5
6
7
juxt2 = ->(*fns) do
  ->(*args) do
    fns.map do |fn, *options|
      options.empty? ? args.send(fn) : args.send(fn, &:"#{options.first}")
    end
  end
end

This version of juxt brings a second meaningful improvement on top of being able to use Enumerable: Now we can juxtapose methods that require a block alongside those where a block is optional:

1
2
juxt2.([:select, :even?], :max, [:reduce, :*]).(1, 2, 3, 4, 5, 6)
# => [[2, 4, 6], 6, 720]

I find this diversion so interesting because I only considered doing things like this in Ruby after I discovered Clojure. There is much to be learned about Ruby by exploring functional programming langugages.