Last week I introduced the concept of Hillis beta reduction and provided an example implementation in Clojure. There were a few caveats to this implementation, however, mostly stemming from the fact that I β-reduced with sequences and vectors rather than the native “xectors” of Hillis’ system. With the risk of adding even more complexity to the demonstration, I’d like to attempt to rectify some of these problems using a few extra tools to transform our data.
Xectors
I won’t provide much detail at all on the xector data type, as I will inevitably botch the majority of the facts. If you’re at all interested in parallel computing, I recommend checking out Hillis’ book, The Connection Machine.
For our purposes, we can consider a xector to be equivalent to a Clojure map1. We can easily redesign our Hillis β-reduction function to take maps as input, but who would want to convert a sequence to a map whenever using the function?
The Xector monad
For this issue we can design a small monad which deliberately breaks the monad laws (cowboy monad?) for demonstration purposes2. The monad will convert provided seqs into an internal map (xector) representation for use in the β-function and (here’s the law-breaking part) leave them as maps when returning results. We could be proper and return the same seq type that was provided, but that would essentially destroy the purpose of the β-reduction in the first place!
Without further ado:
Notice the cheating in m-result
: we return constant values as expected, but non-constant values (i.e., maps made from seqs) are kept as maps.
In m-bind
, we convert any type of seq into a map, and keep any other constant value (in our case, we’ll use numbers) unmodified3.
β-reduction-redux
We define a new multimethod xvals
which dispatches on the result of map?
4. This aligns with the result of the monad bind we defined earlier: for any a
of Xector a
, a
will be either a map or a constant.
Now we can perform β-reduction on seqs by binding them inside our xector-m
:
Traditional folds can now return the expected values without a wrapping map:
Hillis’ arity function
Now that we have a better-ported version of the beta function, I can present a fascinating application also imagined by Hillis later in his paper. It uses both the one- and two-argument forms of the beta function: it simultaneously folds multiple maps into a single map, and then folds that single map to a single value. As always, the code speaks more clearly than I can:
In the inner β-reduce, we reduce a set of keys to the same constant value of 1. When duplicate keys (duplicate occurrences of a value in the provided seq) are found, the value 1 is combined with another value 1 using the function +
, forming a count of 2! This process repeats until the entire provided seq has been digested. Here’s a look at the inner β-reduction by itself (notice that its output matches that of frequencies
!):
Where from here?
To be frank: not a clue! I am having trouble thinking of names for the process, let alone applications. It will most likely remain nothing more than a “thought experiment,” as I said in my previous post. Let me know in the comments below if you have any thoughts!
-
Ignoring all the parallel-processing fun that comes along with xectors, yes. ↩
-
I still don’t fully understand monads, so I may actually be breaking more laws than I intend. ↩
-
I experimented with a
ConstantXector
type and a:constant
metadata key, but both of these methods proved much less elegant than simply leaving the value alone. ↩ -
Dispatches on “mappiness?” ↩