Since version 1.9, Ruby has had
tap started life as Rails
Object#returning. Here’s the old Rails source code:
returning an object and a block and it returns the object, after running the block on that object.
Object#tap is a slight variation on this theme.
Clojure has a similar function called
doto that generalizes the idea. Here’s an example:
The comment in the Ruby on Rails code and Jamis Buck’s, 2006 Mining ActiveSupport:
Object#returning refer to this functionality as an example of the K combinator. Though
doto are useful, and they can really improve your (object-oriented, side-effect-dependent) code by making your intentions plain, they are not examples of the K combinator.
Contrary to Buck’s analysis, a K combinator is not really a “function of two arguments”. Well, in a curried language like Haskell, it is usable as a function of two arguments. But the way that (potentially) two-argument function is almost always used, is as a one-argument function that produces a function. Dig Haskell’s
const, an actual K combinator:
Indeed you see what is potentially a two argument function. But the way
const will be used is:
See what happened there?
const 42 produces a function, which, when passed any parameter, e.g. 0, 1, 2, or 3, will just return 42.
Similarly, Clojure has
(constantly x) that:
Returns a function that takes any number of arguments and returns x.
These are K combinators.
doto are certainly higher-order functions (
doto actually happens to be a macro) in that they take a “function” as a parameter. In the case of
Object#returning the “function” is a block.
doto is a bit more general in that it takes one or more forms, not merely functions.
doto, in general, return a function. Contrast this with
constantly, both of which return functions.
Ruby on Rails’
Object#returning, Ruby 1.9’s
Object#tap, and Clojure’s
doto are useful for clearly delineating code whose only purpose is to introduce side-effects on or about some target object. Examples include:
- tracing for debugging
- multi-step object initialization through setters
These are higher-order functions in that they each take a functional argument (or in
doto’s case, one or more). None of them, however, in general, produces a function.
The K combinator, as exemplified by Haskell’s
const and Clojure’s
constantly, is useful in cases where you need to succinctly construct a function that, irrespective of its argument(s), always returns the same value. A K combinator is a higher-order function that produces a function.
doto are not K combinators.
constantly are. These are two useful, but distinct, concepts.
returning yields its first parameter to the
self and so it only needs a block parameter.