4
votes

In Clojure, there are several option for composition of functions. There are composition functions for:

  • Apply: for 'unwrapping' arguments
  • Partial: for arguments that are not yet given
  • Comp: for piping consecutive results through multiple functions
  • Juxt: for applying one argument on multiple functions

However, AFAIK there are no such composition functions that include branching. Are there any functions that compose functions in a branching way, like a functional version of if or cond ?

Of course an if version is easy to make (though this implementation might not be the quickest):

(defn iff
  ([pred rtrue] (iff pred rtrue identity))
  ([pred rtrue rfalse]
    (fn [& args]
      (if (apply pred args)
        (apply rtrue args)
        (apply rfalse args)))))

There could be discussion about by default returning identity in the 'else' case is the right choice, or if nil should be returned in such case.

The use of such function could produce more easy to read code. Instead of #(if (string? %) (trim %) %) it would become (iff string? trim), or with a cond version:

(condf string? trim,
       vector? (partial apply str),
       :else identity)

Do other FP languages have such constructs ? I can imagine it might be handy in compositions with comp and juxt. Why doesn't Clojure ?

Bonus points for nice iff / condf implementations :)

3

3 Answers

2
votes

I'm not sure if this is a direct match for what you're looking for (the question, to me, is somewhat vague), but you should look into Monads and Arrows.

Monads allow you to chain together functions with a specific "bind" function that defines how to chain them. It could do some sort of if/else pipelining, as in the Maybe and Either monads, or it could simulate state, as in the State monad.

Monads are built into Haskell (as monads) and F# (as "Workflows"). I have seen monad libraries for Clojure (check this out for one), and there are probably Arrow libraries too.

1
votes

This approaches the idea of Strategic Programming. You may find the following paper of interest

The Essence of Strategic Programming by Ralf Lämmel and Eelco Visser and Joost Visser

http://homepages.cwi.nl/~ralf/eosp/

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.20.1969

1
votes

Well there could be many such composition pattern you can come up and ask why this isn't in the core language. The reason is obvious, it is not feasible. The core of the language provide you all the constructs to build such patterns. These sort of features are more of a contrib kind of thing rather than core of the language.

As far as implementation is concerned it would as simple as something shown below:

(defn condf [& args]
  (let [chain (partition 2 args)]
    (fn [& params]
      (first (for [[p f] chain :when (or (= :else p) (apply p params))]
                (apply f params))))))

(def my-func (condf string? clojure.string/trim 
                  vector? (partial apply str)
                  :else identity))

(my-func "Ankur ") ==> "Ankur"
(my-func [1 2 3]) ==> "123"
(my-func '(1 2 3)) ==> (1 2 3)