Skip to content

Instantly share code, notes, and snippets.

@wobh
Last active May 2, 2016 13:49
Show Gist options
  • Save wobh/4e9c40a76425494a1dc205b66a008bee to your computer and use it in GitHub Desktop.
Save wobh/4e9c40a76425494a1dc205b66a008bee to your computer and use it in GitHub Desktop.
pipes via reduction
;; Per https://twitter.com/wobher/status/726442806353399808 what I'm
;; trying to say, all too succinctly, is that structurally an
;; expression like "foo |> bar |> baz |> qux" or even
;; "foo.bar.baz.qux" is commonly considered syntax for a bunch of
;; nested calls: "qux(baz(bar(foo)))". However, in Lisp or any other
;; non-infix language, we can see that this need not be the case.
;; Lets define a `pipe' function; we'd like the following assertions
;; about it to be true:
(assert (= 0
(1+ (lognot 0))
(pipe 0 #'1+ #'lognot)))
(assert (= -2
(lognot (1+ 0))
(pipe 0 #'lognot #'1+)))
;; This should establish that it's non-commutative and the equivalent of
;; nested calls as above. We'd also like it to be a function so that
;; we can compose it's arguments at runtime. This assertion should
;; cover that:
(assert (zerop (funcall #'pipe 0 #'1+ #'lognot)))
;; So how should we implement this? One trick is to look at it's
;; arguments and see that `pipe' with two arguments is the same as
;; `funcall' with two arguments but with the order reversed:
(assert (= 1
(funcall #'1+ 0)
(pipe 0 #'1+)))
;; We could write a recursive definition:
(defun pipe (value &rest functions)
(labels ((pipe-recursive (acc funcs)
(if (null funcs)
acc
(pipe-recursive (funcall (first funcs) acc)
(rest funcs)))))
(pipe-recursive value (reverse functions))))
;; That requires us to reverse an arbitrarily long list of
;; functions. Besides, that use of `funcall' in the recursive call,
;; while syntactically symmetrical, offends our sense of evaluation
;; balance. Fortunately, we can do it iterively any number of ways,
;; but let's forgo almost all of them.
;; Look at what we're doing in the recursive version: we're evaluating
;; each argument pair-wise on the accumulated result of the last pair
;; of accumulator and function. This is a classic reduction,
;; specifically, a right fold. So these assertions must be true:
(assert (= 0
(foldr #'funcall (cons 0 (list #'1+ #'lognot)))
(pipe 0 #'1+ #'lognot)))
(assert (= -2
(foldr #'funcall (cons 0 (list #'lognot #'1+)))
(pipe 0 #'lognot #'1+)))
;; And just look at that beautiful `(cons value (list func1 func2
;; ...))' construction! LISt Processing is the very soul of LISP. How
;; do we do a right fold in Common Lisp?
(defun foldr (function values)
(reduce function
(rest values)
:initial-value (first values)
:from-end t))
;; From here, `pipe' is trivial:
(defun pipe (value &rest functions)
(foldr #'funcall (cons value functions)))
;; All of the assertions are true.
;; In later tweets, I made some remarks about `reduce' being the
;; equivalent of `pipe' rather than the means of `pipe'. I want to
;; concede here, that those statements were either wrong or certainly
;; awkward. My cheif concern was structural, looking at the arguments
;; of `pipe' as a list that can be constructed at runtime is, to my
;; mind, an essential, fundamental technique--something that Lisp
;; makes mentally easier for the programmer by virtue of highly
;; composable syntax, which may lead to highly composable code.
;; I also want to say, it certainly not a fault of the infix `.' or
;; `|>' operations or `->' and `->>' macros that they require the
;; sequence of functions to be known at write-time. They almost always
;; are known already, and write-time conveniences are still
;; conveniences.
;; One could argue that a program that composes arguments to `pipe'
;; would be harder to reason about, which I'll concede so far as it's
;; certainly possible, perhaps even likely. I'll dispute that it's
;; unnecessary--just try writing a program that must perform according
;; to a large domain of command-line options and see if the technique
;; doesn't make it much easier to read and reason about. Composability
;; isn't a virtue for no reason.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment