galdre
galdre

Reputation: 2339

Clojure idioms: sanely pass function-value pairs

Sometimes I want to pass argument-value pairs to a higher-order function, where the value I should pass is determined by the argument I pass. I want to be able to pass the argument without explicitly specifying the accompanying value. In particular, I'm interested in the case where the argument is itself a function.

Generic Example:

Here's a very generic example, where my-foo and my-bar are functions that I'm passing to higher-foo:

(higher-foo my-foo :option4 args) ;good
(higher-foo my-bar :option13 args) ;good
(higher-foo my-foo :option13 args) ;how stupid are you?! my-foo requires :option4!

Question: Is there a "standard" method for making :option4 or :option13 to be inferable by higher-foo so that I can just write (higher-foo my-foo) and (higher-foo my-bar)?

More Specific Example:

Bear in mind that there are better alternatives to the following code, but I'm just trying to put forward a concrete example of what I'm talking about:

(defn seq-has? [f n someseq]
    (every? (partial apply f)
            (partition n 1 someseq)))

(defn monotonicity [a b]
    (<= a b))

(defn generalized-fib [a b c]
    (= c (+ a b)))

(seq-has? monotonicity 2 someseq) should return true if the sequence is monotonic, false otherwise. (seq-has? generalized-fib 3 someseq) should return true if the sequence follows the generalized Fibonacci form, false otherwise.

But the "2" and "3" bother me. I could have an arbitrary number of properties to test for, and I don't want to have to remember the appropriate "magic numbers" for such calls.

Note: I know of two ways to do this, and for my own personal use, I suppose they both work. But I'm interested in what is idiomatic or considered best practice in the community. I'll post my answers, but I'm hoping there are more solutions.

Upvotes: 0

Views: 218

Answers (3)

galdre
galdre

Reputation: 2339

This solution seems like a hack to me. Is it considered common/idiomatic? Use meta-data on the functions that define the property you are looking for:

(defn higher-foo [foo & args]
    (apply foo (:option (meta foo))
               args))

(def my-foo
    (with-meta
        (fn [a b] (println "I'm doing something cool"))
        {:option :option4}))

;using it:
user=> (higher-foo my-foo arg)

Upvotes: -1

Leon Grapenthin
Leon Grapenthin

Reputation: 9266

Since you are asking for a standard way how a function determines a not passed argument from one argument:

(defn f 
  ([arg0] (case a :foo (f a :bar) 
                  :baz (f a :quux)))
  ([arg0 arg1] ...))

Depending on your use case a different dispatch construct than case may be a better fit.

For your generic example this implies that higher-foo should determine the correct :option in the desired overload like demonstrated above.

In your specific example, you can't determine the n from the passed function. You need a more specific datastructure:

(defn seq-has? [{:keys [f n]} s]
  (every? (partial apply f)
          (partition n 1 s)))

(def monotonicity
  {:f <=
   :n 2})

(def generalized-fib
  {:f #(= (+ %1 %2) %3)
   :n 3})

(seq-has? monotonicity [1 2 3])
;; => true

Upvotes: 2

NielsK
NielsK

Reputation: 6956

Just make the predicate function itself take variadic arguments, and have it do the partitioning / recurring. Your monotonic? for instance already exists in core, and is called <=

(<= 1 2 4 5)
=> true
(<= 1 2 1 5)
=> false

Here's the source for the 1, 2 and variadic arg versions:

(source <=)
(defn <=
  "Returns non-nil if nums are in monotonically non-decreasing order,
  otherwise false."
  {:inline (fn [x y] `(. clojure.lang.Numbers (lte ~x ~y)))
   :inline-arities #{2}
   :added "1.0"}
  ([x] true)
  ([x y] (. clojure.lang.Numbers (lte x y)))
  ([x y & more]
   (if (<= x y)
     (if (next more)
       (recur y (first more) (next more))
       (<= y (first more)))
     false)))

You can make a fib? work the same way, have it take variadic arguments and recur over triples:

(defn fib?
  [a b & [c & r]]
  (if (= c (+ a b))
    (if r
      (recur b c r)
      true)
    false))

(fib? 0 1 1)
=> true

(fib? 2 3 5 8 13)
=> true

Upvotes: 4

Related Questions