No, `ifn?` is not what I want. I want to know if a given object is a black box of procedural logic or an updateable bit of "static" data. If it's the former, I can use it to perform actions on data. If it's the latter, I can compare against it, I can introspect it, I can select subsets of it. I don't know ahead of time which will be which because the api is designed to not force the user to specify ahead of time.
Here are some concrete examples of code working around this mismatch:
* clojure.test checks if the first element of a list is a function (in `clojure.test/function?`:
https://github.com/clojure/clojure/blob/13a2f67b91ab81cd109ea3152fce1ae76d212453/src/clj/clojure/test.clj#L424-L434), which changes whether it considers the call to be a "predicate" or not, which affects how reporting is generated.
* Midje allows for functions to be used as the "expected result", and will call the provided function on the test case to determine if the test passes or fails (docs:
https://github.com/marick/Midje/wiki/A-tutorial-introduction#extended-equality-and-checkers, code:
https://github.com/marick/Midje/blob/bee206983db22c6dc92044fd7b5b0365bbd44fc6/src/midje/checking/core.clj#L57). It uses `extended-fn?` which is an implementation of `(or (fn? x) (instance? clojure.lang.MultiFn x))` to work around MultiFn not currently implementing Fn.
* Expectations v2 allows for functions to be used as the "expected result", and will call the provided function on the test case to determine in the test passes or fails (docs:
https://github.com/clojure-expectations/clojure-test, code:
https://github.com/clojure-expectations/clojure-test/blob/9fea7342651e7be1fcd52c85bad599a255228611/src/expectations/clojure/test.cljc#L180-L184). It uses `fn?`, and so this form can't be used with predicate multimethods.
* Compojure has to implement their protocols Renderable and Sendable twice each (once for both clojure.lang.Fn and clojure.lang.MultiFn) to account for this mismatch:
https://github.com/weavejester/compojure/blob/22c56a627522f3343026c3a773630713e1e00eae/src/compojure/response.clj#L46-L49 and
https://github.com/weavejester/compojure/blob/22c56a627522f3343026c3a773630713e1e00eae/src/compojure/response.clj#L67-L73
* The incredibly popular library Orchard (which underpins CIDER and other clojure tools) counts multimethods as functions for purposes of providing information about symbols to developers:
https://github.com/clojure-emacs/orchard/blob/0b3296adf4a38b63cc9b73872d26d6bccb5bc13d/src/orchard/apropos.clj#L73-L79
* The library Dali defines a `function?` predicate that checks if the given object is an `fn?` or MultiFn:
https://github.com/stathissideris/dali/blob/761c383e6f0228bc9e155c3dacea4ee89f708629/src/dali/utils.clj#L33-L35
In each of these instances, the goal isn't to find objects that are callable but to differentiate behavior based on whether the object is intended to be used "as data" (somewhat static collections of information that can be introspected, can be printed, etc) or "as function" (black boxes of procedural logic that can close over state).
Alex is right that there are multiple abstractions that overlap here. All objects exist on a continuum between these two points and in Clojure that line is much blurrier than in other languages, given how many of the core data types can be called like functions. (Compare Clojure's map to Python's dictionary.) However, in my opinion, multimethods exist much closer to the "black box of procedural logic" side than the "collection of information" side.
In a counterfactual world where Rich Hickey added the Fn marker interface to MultiFn when he introduced it, I don't think anyone would be asking to remove it now and I think there would certainly be pushback against such a proposal, pointing to the above continuum.
I think that if protocols were implemented in such a way as to not pass `fn?`, then this would bother me less: `fn` and `defn` are `fn?`, "extendable functions" (multimethods and protocols) are not, write your own `extended-fn?` function and move on.
However, it's an implementation detail that protocols use `(fn ...)` instead of, say, being a `class Protocol implements AFn {}` with 20 `public Object invoke(...) {}` methods. There's now a mismatch between the two "extendable functions".