futils

Function Utilities Library

1    Introduction

futils is a library that adds some abstractions for managing and transforming functions in Clojure.

Currently provided macros and functions are:

  • futils.args/
    • argccounts arguments a function takes (for all arities),
    • relax – transforms a function in a way that it accepts any number of arguments,
    • relax* – like relax but it requires to explicitly describe the accepted arities;
  • futils.named/
    • apply – works like clojure.core/apply but on named arguments,
    • comp – creates a function that is the composition of the given functions,
    • nameize – transforms a function in a way that it can take named arguments,
    • nameize* – like nameize but requires symbols to be quoted;
  • futils.utils/
    • frepeat – returns a sequence generated by a function that uses named arguments.

2    Installation

Add dependencies to project.clj:

[pl.randomseed/futils "1.2.2"]

Then (depending on which functions should be used) require it in your program:

(require 'futils.utils)
(require 'futils.args)
(require 'futils.named)

or:

(ns your-namespace
(:require [futils.utils :as utils])
(:require [futils.args  :as args])
(:require [futils.named :as named]))

3    Usage

3.1    futils.args

The futils.args namespace contains functions that provide positional arguments management, like counting arguments or transforming functions so they can accept variable number of arguments (with optional padding).

3.1.1    argc

(futils.args/argc f & options)

Determines the number of arguments that the given function takes and returns a map containing the following keys:

  • :arities – a sorted sequence containing number of arguments for all arities,
  • :engine:
    • :clj – if metadata were used to determine arities – DEPRECATED);
    • :jvm – if Java reflection methods were used to determine arities),
  • :macro – a flag informing whether the given object is a macro,
  • :variadic – a flag informing whether the widest arity is variadic.

Variadic parameter is counted as one of the possible arguments (if present).

The macro flag (:macro key with value true) is only present when macro was detected.

If the given argument cannot be used to obtain a Var bound to a functon or a function object then it returns nil.

e.3.1  -  Using argc on anonymous functions
(argc (fn ([x y]) ([]))) => {:arities '(0 2) :engine :jvm :variadic false} (argc (fn [a b & c])) => {:arities '(3) :engine :jvm :variadic true}
e.3.2  -  Using argc on named functions
(defn fun ([]) ([a]) ([a b]) ([a b & c])) (argc fun) => {:arities '(0 1 2 3) :engine :jvm :variadic true}
e.3.3  -  Using argc on macros
(defmacro mak ([]) ([a]) ([a b]) ([a b & c])) (argc #'mak) => {:arities '(0 1 2 3) :engine :jvm :macro true :variadic true}
e.3.4  -  Using argc on macros (by symbols)
(defmacro mak ([]) ([a])) (argc mak) => {:arities '(0 1) :engine :jvm :macro true :variadic false}
e.3.5  -  Handling invalid values by argc
(def notfun) (argc 1) => nil (argc nil) => nil (argc "a") => nil (argc notfun) => nil (argc String) => nil

3.1.2    relax

(futils.args/relax f & options)

Returns a variadic function object that calls the given function f, adjusting the number of passed arguments to the nearest matching arity. It cuts argument list or pads it with nil values if necessary.

The arities will be obtained using JVM reflection calls to anonymous class representing a function object.

To determine the number of arguments the nearest arity is picked up by matching a number of passed arguments to number of arguments for each arity. If there is no exact match then the next arity capable of taking all arguments is selected.

If the expected number of arguments is lower than the number of arguments actually passed to a wrapper call then the exceeding ones will be ignored.

If the detected number of arguments that the original function expects is higher than the number of arguments really passed then nil values (or other padding values) will be passed as extra arguments.

When a variadic function is detected and its variadic arity is the closest to a number of arguments passed then all of them will be used during a function call (no argument will be ignored).

The relax takes optional named arguments:

  • :pad-fn – a function that generates values for padding,
  • :pad-val – a value to use for padding instead of nil,
  • :verbose – a switch (defaults to false) that if set to true causes wrapper to return a map containing additional information.

See relax* for detailed descriptions of :pad-fn and :verbose options.

e.3.6  -  Using relax
;; multi-arity function (defn fun ([a b] (list a b)) ([a b c] (list a b c))) (def relaxed (relax fun)) (relaxed) => '(nil nil) (relaxed 1) => '(1 nil) (relaxed 1 2) => '(1 2) (relaxed 1 2 3) => '(1 2 3) (relaxed 1 2 3 4) => '(1 2 3) ;; Var object that refers to a function (def relaxed (relax #'fun)) (relaxed) => '(nil nil) (relaxed 1 2 3 4) => '(1 2 3) ;; anonymous function (def relaxed (relax (fn ([a] (list a)) ([a b c] (list a b c))))) (relaxed) => '(nil) ; matched arity: [a] (relaxed 1) => '(1) ; matched arity: [a] (relaxed 1 2) => '(1 2 nil) ; matched arity: [a b c] (relaxed 1 2 3) => '(1 2 3) ; matched arity: [a b c] (relaxed 1 2 3 4) => '(1 2 3) ; matched arity: [a b c] ;; multi-arity function with variadic argument (defn fun ([a] (list a)) ([a b & more] (list* a b more))) (def relaxed (relax fun)) (relaxed) => '(nil) ; matched arity: [a] (relaxed 1) => '(1) ; matched arity: [a] (relaxed 1 2) => '(1 2) ; matched arity: [a b & more] (relaxed 1 2 3) => '(1 2 3) ; matched arity: [a b & more] (relaxed 1 2 3 4) => '(1 2 3 4) ; matched arity: [a b & more] ;; single-arity function with variadic argument (defn fun2 [& more] more) (def relaxed (relax fun2)) (relaxed) => nil (relaxed 1) => '(1) (relaxed 1 2) => '(1 2) (relaxed 1 2 3) => '(1 2 3) (relaxed 1 2 3 4) => '(1 2 3 4)
e.3.7  -  Custom padding
;; padding value as :pad-val (def relaxed (relax #(list %1 %2 %3) :pad-val :nic)) (relaxed) => '(:nic :nic :nic) (relaxed 1) => '(1 :nic :nic) (relaxed 1 2) => '(1 2 :nic) (relaxed 1 2 3) => '(1 2 3) (relaxed 1 2 3 4) => '(1 2 3) ;; padding function (defn padder [& {:keys [previous] :or {previous -1}}] (inc previous)) (def relaxed (relax #(list %1 %2 %3) :pad-fn padder)) (relaxed) => '(0 1 2) (relaxed 1) => '(1 2 3) (relaxed 5) => '(5 6 7) (relaxed 1 8) => '(1 8 9)
e.3.8  -  Verbose mode
(defn fun ([a] (list a)) ([a b & more] (list* a b more))) (def relaxed (relax fun :verbose true)) (relaxed) => {:argc-cutted 0 :argc-padded 1 :argc-received 0 :argc-sent 1 :args-received () :args-sent '(nil) :arities '(1 3) :arity-matched 1 :engine :jvm :result '(nil) :variadic true :variadic-used false :verbose true} (relaxed 1 2 3) => {:argc-cutted 0 :argc-padded 0 :argc-received 3 :argc-sent 3 :args-received '(1 2 3) :args-sent '(1 2 3) :arities '(1 3) :arity-matched 3 :engine :jvm :result '(1 2 3) :variadic true :variadic-used true :verbose true}
e.3.9  -  Handling invalid values
(def notfun) (relax 1) => (throws java.lang.AssertionError) (relax nil) => (throws java.lang.AssertionError) (relax "a") => (throws java.lang.AssertionError) (relax notfun) => (throws java.lang.AssertionError) (relax String) => (throws java.lang.AssertionError)

3.1.3    relax*

(futils.args/relax* f & options)

Returns a variadic function object that calls the given function, adjusting the number of passed arguments to nearest matching arity. It cuts argument list or pads it with nil values if necessary.

It takes 1 positional, obligatory argument, which should be a function (f) and two named, keyword arguments:

  • :arities – a sorted sequence of argument counts for all arities,
  • :variadic – a flag informing whether the widest arity is variadic.

It also makes use of optional named arguments:

  • :pad-fn – a function that generates values for padding,
  • :pad-val – a value to use for padding instead of nil,
  • :verbose – a switch (defaults to false) that if set to true, causes wrapper to return a map containing additional information.

To determine the number of arguments the nearest arity is picked up by matching a number of passed arguments to each number from a sequence (passed as :arities keyword argument). If there is no exact match then the next arity capable of handling all arguments is chosen.

If the expected number of arguments is lower than a number of arguments actually passed to a wrapper call, the exceeding ones will be ignored.

If the declared number of arguments that the original function expects is higher than the number of arguments really passed then nil values (or other padding values) will be placed as extra arguments.

When a variadic function is detected and its variadic arity is the closest to the number of passed arguments then all of them will be used.

3.1.3.1    Verbose mode

If the :verbose flag is set then the result will be a map containing the following:

  • :argc-received – a number of arguments received by the wrapper,
  • :argc-sent – a number of arguments passed to a function,
  • :argc-cutted – a number of arguments ignored,
  • :argc-padded – a number of arguments padded with nil values,
  • :args-received – arguments received by the wrapper,
  • :args-sent – arguments passed to a function,
  • :arities – a sorted sequence of argument counts for all arities,
  • :arity-matched – an arity (as a number of arguments) that matched,
  • :engine – a method used to check arities (:clj or :jvm),
  • :result – a result of calling the original function,
  • :variadic – a flag telling that the widest arity is variadic,
  • :variadic-used – a flag telling that a variadic arity was used,
  • :verbose – a verbosity flag (always true in this case).

3.1.3.2    Padding function

If a padding function is given (with :pad-fn) it should take keyword arguments. During each call the following keys will be set:

  • :argc-received – a number of arguments received by the wrapper,
  • :arity-matched – an arity (as a number of arguments) that matched,
  • :iteration – a number of current iteration (starting from 1),
  • :iterations – a total number of iterations,
  • :previous – a value of previously calculated argument (the result of a previous call or a value of the last positional argument when padding function is called for the first time).

Values associated with :iteration and :previous keys will change during each call, the rest will remain constant.

If there is no last argument processed at a time when f is called for the first time (because no arguments were passed), the :previous key is not added to a passed map. That allows to use a default value in a binding map of f or to make easy checks if there would be some previous value (nil is ambiguous).

e.3.10  -  Using relax*
(defn fun ([a b] (list a b)) ([a b c] (list a b c))) (def relaxed (relax* fun :arities [2 3])) (relaxed) => '(nil nil) (relaxed 1) => '(1 nil) (relaxed 1 2) => '(1 2) (relaxed 1 2 3) => '(1 2 3) (relaxed 1 2 3 4) => '(1 2 3)
e.3.11  -  Handling variadic arguments by relax*
(defn fun ([a] (list a)) ([a b & more] (list* a b more))) (def relaxed (relax* fun :arities '(3 1) :variadic true)) (relaxed) => '(nil) ; matched arity: [a] (relaxed 1) => '(1) ; matched arity: [a] (relaxed 1 2 3 4) => '(1 2 3 4) ; matched arity: [a b & more] (defn fun2 [& more] more) (def relaxed (relax* fun2 :arities [1] :variadic true)) (relaxed) => nil (relaxed 1) => '(1) (relaxed 1 2 3 4) => '(1 2 3 4)
e.3.12  -  Using relax* on anonymous functions
(def relaxed (relax* (fn ([a] (list a)) ([a b c] (list a b c))) :arities [3 1])) (relaxed) => '(nil) ; matched arity: [a] (relaxed 1) => '(1) ; matched arity: [a] (relaxed 1 2) => '(1 2 nil) ; matched arity: [a b c] (relaxed 1 2 3 4) => '(1 2 3)
e.3.13  -  Custom padding value
(def relaxed (relax* #(list %1 %2 %3) :arities [3] :pad-val :nic)) (relaxed) => '(:nic :nic :nic) (relaxed 1) => '(1 :nic :nic) (relaxed 1 2 3 4) => '(1 2 3)
e.3.14  -  Custom padding function
(defn padder [& {:keys [previous] :or {previous -1}}] (inc previous)) (def relaxed (relax* #(list %1 %2 %3) :arities [3] :pad-fn padder)) (relaxed) => '(0 1 2) (relaxed 1) => '(1 2 3) (relaxed 5) => '(5 6 7) (relaxed 1 8) => '(1 8 9)
e.3.15  -  Verbose mode
(defn fun ([a] (list a)) ([a b & more] (list* a b more))) (def relaxed (relax* fun :arities [3 1] :variadic true :verbose true)) (relaxed) => {:argc-cutted 0 :argc-padded 1 :argc-received 0 :argc-sent 1 :args-received () :args-sent '(nil) :arities '(1 3) :arity-matched 1 :result '(nil) :variadic true :variadic-used false :verbose true} (relaxed 1 2 3) => {:argc-cutted 0 :argc-padded 0 :argc-received 3 :argc-sent 3 :args-received '(1 2 3) :args-sent '(1 2 3) :arities '(1 3) :arity-matched 3 :result '(1 2 3) :variadic true :variadic-used true :verbose true}
e.3.16  -  Chaining with argc
(defn fun ([a b] (list a b)) ([a b c] (list a b c))) (def relaxed (mapply relax* fun (argc fun))) (relaxed) => '(nil nil) (relaxed 1) => '(1 nil) (relaxed 1 2) => '(1 2) (relaxed 1 2 3) => '(1 2 3) (relaxed 1 2 3 4) => '(1 2 3)
e.3.17  -  With great power comes great responsibility
(defn fun ([a b] (list a b)) ([a b c] (list a b c))) (def relaxed (relax* fun :arities [1 2 5])) ; wrong arities! (relaxed) => (throws clojure.lang.ArityException) (relaxed 1) => (throws clojure.lang.ArityException) (relaxed 1 2) => '(1 2) (relaxed 1 2 3) => (throws clojure.lang.ArityException) (relaxed 1 2 3 4) => (throws clojure.lang.ArityException)
e.3.18  -  Handling invalid values
(defn fun []) (def notfun) (relax* []) => (throws java.lang.AssertionError) (relax* #() #()) => (throws java.lang.IllegalArgumentException) (relax* nil nil) => (throws java.lang.IllegalArgumentException) (relax* :arities [0] #()) => (throws java.lang.AssertionError) (relax* #() :arities []) => (throws java.lang.AssertionError) (relax* #() :arities nil) => (throws java.lang.AssertionError) (relax* #() :arities 123) => (throws java.lang.IllegalArgumentException) (relax* 1 :arities []) => (throws java.lang.AssertionError) (relax* 1 :arities []) => (throws java.lang.AssertionError) (relax* nil :arities [0]) => (throws java.lang.AssertionError) (relax* "a" :arities [0]) => (throws java.lang.AssertionError) (relax* notfun :arities [0]) => (throws java.lang.AssertionError) (relax* String :arities [0]) => (throws java.lang.AssertionError) (relax* #'fun :arities [0]) => (throws java.lang.AssertionError)

3.2    futils.named

The futils.named namespace contains functions transforming functions taking positional arguments into functions that can handle named arguments.

3.2.1    apply

(futils.utils/apply f args* args-map)

It works like apply but handles named arguments. Takes function f, an optional list of arguments (args*) to be passed during a call to it and a map (args-map) that will be decomposed and passed as named arguments.

Returns the result of calling f.

e.3.19  -  Usage of apply
(named/apply assoc {} {:a 1 :b 2 :c 3}) => {:a 1 :b 2 :c 3} (defn fun [& {:as args}] args) (named/apply fun {:a 1 :b 2 :c 3}) => {:a 1 :b 2 :c 3} (defn fun [a b & {:as args}] (sort (list* a b (vals args)))) (named/apply fun 10 20 {:a 1 :b 2 :c 3}) => '(1 2 3 10 20)
e.3.20  -  Handling invalid values by apply
(def notfun) (named/apply) => (throws clojure.lang.ArityException) (named/apply 1) => (throws java.lang.ClassCastException) (named/apply nil) => (throws java.lang.NullPointerException) (named/apply "a") => (throws java.lang.ClassCastException) (named/apply notfun) => (throws java.lang.IllegalStateException) (named/apply String) => (throws java.lang.ClassCastException)

3.2.2    comp

(futils.named/comp function…) (futils.named/comp options-map function…) (futils.named/comp function… options-map)

where function is a function object or a map containing :f key associated with a function object and optional options controlling output transformations.

The comp function takes a set of functions that accept named arguments and returns a function object that is the composition of those functions. The returned function takes named arguments, applies the rightmost of functions to the arguments, the next function (right-to-left) to the result, etc.

Each function should return a map or a sequential collection (see :use-seq switch) that will be used to generate named arguments for the next function in the execution chain. If a function does not return a map its resulting value will be assigned to a key of newly created map. The default name of this key will be :out unless the option :map-output had been used (see the explanations below).

The returned value of the last called function is not transformed in any way and there is no need for it to be a map.

Functions can be expressed as function objects or as maps. In the second case the map must contain :f key with function object assigned to it and may contain optional, controlling options which are:

  • :merge-args:  false (default) or true or a key,
  • :map-output:  false (default) or true or a key,
  • :rename-keys:    nil (default) or a map,
  • :post-rename:    nil (default) or a map,
  • :use-seq:            nil (default) or true,
  • :apply-raw:        nil (default) or true.

The :merge-args option, when is not set to false nor nil, causes function arguments to be merged with a returned map. If the key name is given they all will be stored under specified key of this map. If the assigned value is set to true then they will be merged. If two keys are the same the association from arguments is overwritten by the entry being returned by a function.

The :map-output causes the returned value to be stored under a specified key of a resulting map. If the option value is set to true then the key name will be :out.

The :rename-keys option causes keys of a resulting map to be renamed according to the given map. The transformation will be performed on a returned value (if it's a map), before any other changes (output mapping or arguments merging).

The :post-rename option works in the same way as :rename-keys but it's performed after all other transformations are applied.

The :use-seq option has the effect only if the returned value is a sequential collection having even number of arguments. If it's set then the sequence is changed into a map for further processing (including renaming keys) instead of being put as a value associated with some key.

The :apply-raw option is for performance reasons. Use it with care. It disables checks and most of the transformations and causes wrapper to assume that the resulting structure is either single value, sequential collection or a map. If it's an atomic value or a map, it will change it into sequence ready to be applied as named arguments when calling the next function. If it's a sequence then nothing will be changed.

Defaults for the options described above may be given by passing a map as a first or last argument when calling the comp function. Such a map should not contain :f key.

The function returns a function object.

e.3.21  -  Basic usage of comp
(defn f1 [& {:as args}] (assoc args :f1 1)) (defn f2 [& {:as args}] (assoc args :f2 2)) (defn f3 [& {:as args}] (assoc args :f3 3)) (def nfun (named/comp f1 f2 f3)) (nfun) => {:f1 1 :f2 2 :f3 3} (nfun :a 1) => {:f1 1 :f2 2 :f3 3 :a 1} (nfun :a 1 :b 2) => {:f1 1 :f2 2 :f3 3 :a 1 :b 2} (nfun :a 1 :b 2 :c 3) => {:f1 1 :f2 2 :f3 3 :a 1 :b 2 :c 3} (nfun :a 1 :b 2 :c 3 :d 4) => {:f1 1 :f2 2 :f3 3 :a 1 :b 2 :c 3 :d 4} (def nfun (named/comp f1)) (nfun) => (just [:f1 1] :in-any-order) (nfun :a 1) => (just [:f1 1 :a 1] :in-any-order) (nfun :a 1 :b 2) => (just [:f1 1 :a 1 :b 2] :in-any-order) (nfun :a 1 :b 2 :c 3) => (just [:f1 1 :a 1 :b 2 :c 3] :in-any-order) (nfun :a 1 :b 2 :c 3 :d 4) => (just [:f1 1 :a 1 :b 2 :c 3 :d 4] :in-any-order) (def nfun (named/comp)) (nfun) => nil (nfun :a 1) => (just [:a 1] :in-any-order) (nfun :a 1 :b 2) => (just [:a 1 :b 2] :in-any-order) (nfun :a 1 :b 2 :c 3) => (just [:a 1 :b 2 :c 3] :in-any-order) (nfun :a 1 :b 2 :c 3 :d 4) => (just [:a 1 :b 2 :c 3 :d 4] :in-any-order)
e.3.22  -  Merging arguments with comp
(defn f1 [& {:as args}] (assoc args :f1 1)) (defn f2 [& {:as args}] (assoc args :f2 2)) (defn f3 [& {:as args}] (assoc args :f3 3)) (def nfun (named/comp f1 {:f f2 :merge-args :in} f3)) (nfun) => {:f1 1 :f2 2 :f3 3 :in {:f3 3}} (nfun :a 1) => {:f1 1 :f2 2 :f3 3 :a 1 :in {:a 1 :f3 3}} ;; Now the f3 function is not passing args on its own. ;; Instead it emits a simple value. (defn f3 [& {:as args}] 123456) ;; Normally it will ignore args. ((named/comp f1 f2 f3) :a 1) => {:f1 1 :f2 2 :out 123456} ;; With :merge-args set to some value it will merge args ;; with the resulting map under the given key. ((named/comp f1 f2 {:f f3 :merge-args :f3-in}) :a 1) => {:f1 1 :f2 2 :f3-in {:a 1} :out 123456} ;; With :merge-args set to true it will merge args ;; with the resulting map. ((named/comp f1 f2 {:f f3 :merge-args true}) :a 1) => {:f1 1 :f2 2 :a 1 :out 123456} ;; It won't overwrite conflicting keys. ((named/comp f1 f2 {:f f3 :merge-args true}) :out 1111111) => {:f1 1 :f2 2 :out 123456} ((named/comp f1 f2 {:f f3 :merge-args true}) :f2 1111111) => {:f1 1 :f2 2 :out 123456} (defn f3 [& {:as args}] {:x 2}) ((named/comp f1 f2 {:f f3 :merge-args true}) :x 1111111 :y 4) => {:f1 1 :f2 2 :x 2 :y 4}
e.3.23  -  Mapping output with comp
(defn f1 [& {:as args}] (assoc args :f1 1)) (defn f2 [& {:as args}] (assoc args :f2 2)) (defn f3 [& {:as args}] (assoc args :f3 3)) ((named/comp f1 f2 {:f f3 :map-output true}) :a 1 :b 2) => {:f1 1 :f2 2 :out {:a 1 :b 2 :f3 3}} ((named/comp f1 f2 f3 {:map-output true}) :a 1 :b 2) => {:f1 1 :out {:f2 2 :out {:a 1 :b 2 :f3 3}}} ((named/comp f1 f2 {:f f3 :map-output :r}) :a 1 :b 2) => {:f1 1 :f2 2 :r {:a 1 :b 2 :f3 3}} ((named/comp f1 f2 f3 {:map-output :r}) :a 1 :b 2) => {:f1 1 :r {:f2 2 :r {:a 1 :b 2 :f3 3}}}
e.3.24  -  Renaming results with comp
(defn f1 [& {:as args}] (assoc args :f1 1)) (defn f2 [& {:as args}] (assoc args :f2 2)) (defn f3 [& {:as args}] (assoc args :f3 3)) ((named/comp f1 f2 {:f f3 :rename-keys {:a :b :b :c}}) :a 1 :b 2) => {:b 1 :c 2 :f1 1 :f2 2 :f3 3} ((named/comp f1 f2 {:f f3 :map-output true :rename-keys {:a :x}}) :a 1) => {:f1 1 :f2 2 :out {:f3 3 :x 1}} ((named/comp f1 f2 {:f f3 :map-output true :post-rename {:out :result}}) :a 1) => {:f1 1 :f2 2 :result {:f3 3 :a 1}} ((named/comp f1 f2 {:f f3 :map-output true :rename-keys {:a :x} :post-rename {:out :result}}) :a 1) => {:f1 1 :f2 2 :result {:f3 3 :x 1}}
e.3.25  -  Using sequential results with comp
(defn f1 [& {:as args}] (assoc args :f1 1)) (defn f2 [& {:as args}] (concat (mapcat identity args) [:f2 2])) (defn f3 [& {:as args}] (assoc args :f3 3)) ((named/comp f1 {:f f2 :use-seq true} f3) :a 1 :b 2 :c 3 :d 4 :e 5) => {:a 1 :b 2 :c 3 :d 4 :e 5 :f1 1 :f2 2 :f3 3} ((named/comp f1 f2 f3 {:use-seq true}) :a 1 :b 2 :c 3 :d 4 :e 5) => {:a 1 :b 2 :c 3 :d 4 :e 5 :f1 1 :f2 2 :f3 3} ((named/comp f1 {:f f2 :use-seq true} {:f f3 :rename-keys {:a :b :b :c}}) :a 1 :b 2) => {:b 1 :c 2 :f1 1 :f2 2 :f3 3} ((named/comp f1 {:f f2 :use-seq true} {:f f3 :map-output true :rename-keys {:a :x}}) :a 1) => {:f1 1 :f2 2 :out {:f3 3 :x 1}} ((named/comp f1 {:f f2 :use-seq true} {:f f3 :map-output true :post-rename {:out :result}}) :a 1) => {:f1 1 :f2 2 :result {:f3 3 :a 1}} ((named/comp f1 f2 {:f f3 :map-output true :rename-keys {:a :x} :post-rename {:out :result}} {:use-seq true}) :a 1) => {:f1 1 :f2 2 :result {:f3 3 :x 1}}
e.3.26  -  Using raw results with comp
(defn f1 [& {:as args}] (assoc args :f1 1)) (defn f2 [& {:as args}] (concat (mapcat identity args) [:f2 2])) (defn f3 [& {:as args}] (concat (mapcat identity args) [:f3 3])) ((named/comp f1 {:f f2 :apply-raw true} {:f f3 :apply-raw true}) :a 1 :b 2 :c 3 :d 4 :e 5) => {:a 1 :b 2 :c 3 :d 4 :e 5 :f1 1 :f2 2 :f3 3} ((named/comp f1 f2 f3 {:apply-raw true}) :a 1 :b 2 :c 3 :d 4 :e 5) => {:a 1 :b 2 :c 3 :d 4 :e 5 :f1 1 :f2 2 :f3 3}

3.2.3    nameize

(futils.named/nameize f [names]) (futils.named/nameize f [names] {defaults}) (futils.named/nameize f arity-mappings…)

where arity-mappings… are pairs expressed as: [names] {defaults} or just [names].

The macro creates a wrapper that passes named arguments as positional arguments. It takes a funtion object (f), a vector S-expression containing names of expected arguments (names – expressed as keywords, symbols, strings or other objects) and an optional map S-expression with default values for named arguments (defaults).

Since version 0.7.0 it accepts multiple arity mappings expressed as pairs consisting of argument name vectors and maps of default values (for all or some of the names).

The order of names in a vector is important. Each name will become a key of named argument which value will be passed to the given function on the same position as in the vector.

If unquoted symbol is given in a vector or in a map, it will be transformed into a keyword of the same name. Use quoted symbols if you want to use symbols as keys of named arguments.

If the &rest special symbol is placed in a vector then the passed value that corresponds to its position will be a map containing all named arguments that weren't handled. If there are none, nil value is passed.

The macro is capable of handling multiple arities. In such case the declared arities (e.g. [:a :b] [:a :b :c]) will be matched against the given named arguments (e.g. {:a 1 :b 2}) by comparing declared argument names to key names. Firstly it will try to match them without considering default values (if any) and in case of no success (when there is no declared arity that can be satisfied by the given arguments) matching is preformed again but with default arguments merged. From the resulting collection of matching arity mappings the one element with the least requirements is chosen (that has the lowest count of declared arguments).

The result is a function object.

e.3.27  -  Usage of nameize
(def nreduce (nameize reduce [f coll] [f val coll])) (nreduce :f + :coll [1 2 3 4]) => 10 (nreduce :f + :coll [1 2 3 4] :val 2) => 12 (def nfun (nameize #(list %1 %2) [a b])) (nfun :a 1 :b 2) => '(1 2) (def nfun (nameize (fn [& a] a) [a b] {:a 7 :b 8})) (nfun :b 2) => '(7 2) (defn fun [& more] more) (def nfun (nameize fun [a b c])) (nfun :a 1 :b 2 :c 3 :d 4) => '(1 2 3) (def nfun (nameize fun [a b c &rest])) (nfun :a 1 :b 2 :c 3 :d 4) => '(1 2 3 {:d 4}) (def nfun (nameize fun [a &rest b c] {:a 1})) (nfun :b 2 :c 3 :d 4) => '(1 {:d 4} 2 3) (def nfun (nameize fun [a b c &rest])) (nfun :a 1 :b 2 :c 3) => '(1 2 3 nil)
e.3.28  -  Handling multiple arities by nameize
(def nfun (nameize (fn [& a] a) [a] {:a 5} [a c] [a b &rest] [a b c &rest] {:a 1 :e 5})) (nfun) => '(5) ; matched: [a] (nfun :a 1) => '(1) ; matched: [a] (nfun :a 1 :c 3) => '(1 3) ; matched: [a c] (nfun :a 1 :b 2) => '(1 2 nil) ; matched: [a b &rest] (nfun :a 1 :b 2 :c 3) => '(1 2 3 {:e 5}) ; matched: [a b c &rest] (nfun :a 1 :b 2 :c 3 :d 4) => '(1 2 3 {:d 4 :e 5})
e.3.29  -  Handling invalid values by nameize
(def notfun) (defn fun [a b] (list a b)) (nameize notfun []) => (throws java.lang.AssertionError) (def nfun (nameize fun [a b])) (nfun :a 1) => (throws java.lang.IllegalArgumentException) (nameize fun [:a] [:b] [:a b]) => (throws java.lang.IllegalArgumentException)

3.2.4    nameize*

(futils.named/nameize* f names) (futils.named/nameize* f names defaults) (futils.named/nameize* f arity-mappings…)

where arity-mappings is a pair expressed as: names defaults or just names.

The function creates a wrapper that passes named arguments as positional arguments. It takes a funtion object (f), a vector S-expression containing names of expected arguments (names – expressed as keywords, symbols, strings or other objects) and an optional map S-expression with default values for named arguments (defaults).

Since version 0.7.0 it accepts multiple arity mappings expressed as pairs consisting of argument name vectors and maps of default values (for all or some of the names).

The order of names in a vector is important. Each name will become a key of named argument which value will be passed to the given function on the same position as in the vector.

If unquoted symbol is given in a vector or in a map, it will be transformed into a keyword of the same name. Use quoted symbols if you want to use symbols as keys of named arguments.

If the &rest special symbol is placed in a vector then the passed value that corresponds to its position will be a map containing all named arguments that weren't handled. If there are none, nil value is passed.

The function is capable of handling multiple arities. In such case the declared arities (e.g. [:a :b] [:a :b :c]) will be matched against the given named arguments (e.g. {:a 1 :b 2}) by comparing declared argument names to key names. Firstly it will try to match them without considering default values (if any) and in case of no success (when there is no declared arity that can be satisfied by the given arguments) matching is preformed again but with default arguments merged. From the resulting collection of matching arity mappings the one element with the least requirements is chosen (that has the lowest count of declared arguments).

A function object is returned.

e.3.30  -  Usage of nameize*
(def nfun (nameize* #(list %1 %2) ['a :b] {})) (nfun 'a 1 :b 2) => '(1 2) (def nfun (nameize* (fn [& a] a) '[a b] '{a 7 b 8})) (nfun 'b 2) => '(7 2) (defn fun [& more] more) (def nfun (nameize* fun '[a b c] {})) (nfun 'a 1 'b 2 'c 3 'd 4) => '(1 2 3) (def nfun (nameize* fun [:a :b :c '&rest] {})) (nfun :a 1 :b 2 :c 3 :d 4) => '(1 2 3 {:d 4}) (def nfun (nameize* fun [:a '&rest :b :c] {:a 1})) (nfun :b 2 :c 3 :d 4) => '(1 {:d 4} 2 3) (def nfun (nameize* fun '[:a :b :c &rest] {})) (nfun :a 1 :b 2 :c 3) => '(1 2 3 nil)
e.3.31  -  Handling multiple arities by nameize*
(def nfun (nameize* (fn [& a] a) '[:a] {:a 5} '[:a :b] {} '[:a :b &rest] {} '[:a :b :c &rest] {:a 1 :e 5})) (nfun) => '(5) ; matched: [a] (nfun :a 1) => '(1) ; matched: [a] (nfun :a 1 :b 2) => '(1 2) ; matched: [a b] (nfun :a 1 :b 2 :c 3) => '(1 2 3 {:e 5}) ; matched: [a b c &rest] (nfun :a 1 :b 2 :c 3 :d 4) => '(1 2 3 {:d 4 :e 5})
e.3.32  -  Handling invalid values by nameize*
(def notfun) (nameize* notfun [] {}) => (throws java.lang.AssertionError) (defn fun [a b] (list a b)) (def nfun (nameize* fun [:a :b] {})) (nfun :a 1) => (throws java.lang.IllegalArgumentException)

3.3    futils.utils

The futils.utils namespace contains functions providing various additional operations on functions and some helpers used by other parts of library.

3.3.1    frepeat

(futils.utils/frepeat n? f kvs?)

Returns a lazy sequence of results produced by the given function f which should accept named arguments.

If the first argument passed to frepeat is a number (n) and the second is a function (f) it will limit the iterations to the specified count.

If the numeric argument is missing and only a function object is given the frepeat will produce infinite sequence of calls.

The last, optional argument should be a map (kvs) that initializes named arguments that will be passed to the first and subsequent calls to f.

Additionally each call to f will pass the following keyword arguments:

  • :iteration – a number of current iteration (starting from 1),
  • :previous – a result of the previous call to f.

The first call to f will pass the following:

  • :iteration – 1,
  • :iterations – a total number of iterations (if n was given).

It is possible to set the initial value of :previous if there is a need for that (by passing it to frepeat) or shadow the value assigned to :iterations after the first call (by setting it in the passed function f).

Values associated with :iteration and :previous keys will always change during each call.

e.3.33  -  Basic usage of frepeat
(take 5 (frepeat (constantly :x))) => '(:x :x :x :x :x) (frepeat 5 (constantly :x)) => '(:x :x :x :x :x) (frepeat 5 (fn [& {:keys [iteration]}] iteration)) => '(1 2 3 4 5)
e.3.34  -  Using frepeat on anonymous functions
(def repeater #(inc (:previous (apply array-map %&)))) (def numbers (frepeat repeater {:previous 0})) (take 5 numbers) => '(1 2 3 4 5) (frepeat 5 (fn [& {:keys [previous] :or {previous 9}}] (inc previous))) => '(10 11 12 13 14)
e.3.35  -  Using frepeat on named functions
(defn repeater [& {:keys [previous iteration iterations a]}] [iteration iterations a]) (frepeat 5 repeater {:a :something}) => '([1 5 :something] [2 5 :something] [3 5 :something] [4 5 :something] [5 5 :something])
e.3.36  -  Handling invalid values by frepeat
(def notfun) (frepeat) => (throws clojure.lang.ArityException) (frepeat 1) => (throws java.lang.ClassCastException) (frepeat nil) => (throws java.lang.NullPointerException) (frepeat "a") => (throws java.lang.ClassCastException) (frepeat notfun) => (throws java.lang.IllegalStateException) (frepeat String) => (throws java.lang.ClassCastException)