You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Holger Schauer b36776aeb3 Document ^:primary use for choice of default. 7 years ago
doc Document ^:primary use for choice of default. 7 years ago
src/de/find_method Handle metadata given with ^ reader macro. 7 years ago
test/de/find_method Handle metadata given with ^ reader macro. 7 years ago
.gitignore Signature registration and function prototype. 7 years ago
LICENSE Signature registration and function prototype. 7 years ago Document ^:primary use for choice of default. 7 years ago
project.clj Document ^:primary use for choice of default. 7 years ago


funsig is a Clojure library designed to inject function implementations for function signatures.

Clojure provides multiple ways of inversing dependencies. Some are built-in, like multi-methods or protocols, some use libraries, like Stuart Sierra's component library. None of these, however, handle the most basic problem where some function depends on a single other function. funsig allows you to define a function signature independently from the implementation. Once you provide an implementation, you can simply refer to the signature of the function to use it, thereby inversing the dependency.


For the latest release, add the following dependency to your project.clj:

Clojars Project


Funsig assumes that you define the signature and afterwards the implementation. Do use this, require the two main macros defsig and defimpl:

	(ns my.onion
		(:require [de.find-method.funsig :as di :refer [defsig defimpl]]))

You can then define the signature of the function your application level code has a dependency on with defsig:

	(defsig printer [string])

	(defn print-account-multiplied [account multiplier]
		(let [result (* account multiplier)]
			(printer result)))

You also need to supply an implementation with defimpl:

	(ns my.onion.printer
		(:require [de.find.method.funsig :as di :refer [defimpl]]
			      [my.onion :as mo :refer [printer]]))

	(defimpl printer [string]
		(println string))

Note that the implementation has a dependency on the signature, not the other way around -- your application code (print-account-multiplied) simply depends on the signature. Obviously, you need to load (require) the code defining the implementation somewhere -- if you get an error calling print-account-multiplied telling you there is no implementation for printer, this tells you that you never loaded my.onion.printer.

An important thing to know is that the parameter list of the signature and the implementation need to agree -- currently, agreement means equality, not compatibility. Hence, both signature and implementation have the exact same parameter list [string].

Argument destructuring and variadic function (implementations) are supported, but again, note that the argument lists need to be equal currently. You can also provide docstrings and an argument map that will be added as meta-data as per defn.

	(defsig another-sig "Expects one or two arguments" ([] [arg1]))

    (defimpl another-sig
			(println "No argument received"))
			(println "One argument received")

Handling multiple implementations of a signature

If you have multiple implementations for a signature in different namespaces, you should explicitly declare which implementation you want, otherwise the load order of the modules will determine which default implementation you get (the defimpl loaded last will win). You can determine a default implementation by setting the :primary key as meta data on the implementation:

	(ns my.onion.fancy-printer
		(:require [de.find.method.funsig :as di :refer [defimpl]]
			      [my.onion :as mo :refer [printer]]))

	(defimpl ^:primary printer [string]
		(println "Fancy print" string))

Alternatively, if you don't want to specify the default implementation with the definition itself, you can set a default implementation via set-default-implementation!, like so:

		(:require [de.find-method.funsig :as di :refer [set-default-implementation!]]
			      [my.onion.printersig :refer [printer]]
				  [my.onion.fancy-printer :refer [printer-impl]]))

	(set-default-implementation! printer printer-impl)

You can do this with set-default-implementation! which expects the name of the signature and the name of the implementation -- the latter consists of the name of the signature plus -impl:

		(:require [de.find-method.funsig :as di :refer [set-default-implementation!]]
			      [my.onion.printersig :refer [printer]]
				  [my.onion.printerimpl2 :as impl2 :refer [printer-impl]]))

	(set-default-implementation! printer impl2/printer-impl)

Using a namespace prefix in front of the implementation function is good practice here.

You could use this feature to easily supply a different implementation in tests, e.g. for mock purposes.

Using clean slate in tests

Funsig holds state information about signatures and implementations. Adding a new (mock) implementation for a signature can lead to pollution of the state that you might want to avoid in your tests.

Funsig is nothing more but a small set of macros plus a ServiceLocator record. If you just want to test a pair of signature and implementation without polluting the global service locator, simply bind de.find-method.funsig/*locator* to a freshly started locator (started via start-new-locator) like this (using midje here):

	(ns my.onion.fetchsig-test
		(:use midje.sweet)
		(:require [de.find.method.funsig :as di :refer [defsig defimpl *locator*]]
			      [de.find.method.funsig.core :as dicore :refer [start-new-locator]]))

	(fact "Invoking an implementation for a signature by calling the signature works"
		(binding [*locator* (start-new-locator)]
			(do (defsig fetch-foo [foo bar])
				(defimpl fetch-foo [foo bar] [foo bar])
					(fetch-foo 1 2)) => [1 2]))

With a new *locator* binding, you'll need to call both defsig and defimpl.

More details

If you need some more details about the internals, refer to the intro document.


Copyright © 2015 Holger Schauer

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.