In a number of occasions I have a collection of functions that I'd like to implement in different ways. The most obvious example of this would be to abstract from specific databases. In an object-oriented language you would use an interface for this:
interface DB {
ResultSet query(String query);
void persist(Object o);
...
}
In speudo code I would want to do something like this:
(ns dbbackend)
(abstractfn query [q])
(abstractfn persist! [o])
And then implementations for each database:
(ns dbbackend.mysql :implements dbbackend)
(defn query [q] ...)
(defn persist! [o] ...)
It is not entirely clear to me what the best practice is to do something similar in a functional language, specifically Clojure. Should I use multi-methods for this?
Now that version 1.1 of Clojure has been released maybe it's time to take a look into the future.
Datatypes and protocols, which are currently only available in the new master branch on github , might be exactly what you are looking for.
(defprotocol DB
(query [backend query])
(persist [backend object]))
(deftype MySQLBackend []
DB
(query [query] ...)
(persist [object] ...))
For pre-protocol Clojure versions:
The interface:
(ns dbbackend)
(defmulti query
{:arglists '([c q])}
suitable-dispatch-fn)
(defmulti persist!
{:arglists '([c o])}
suitable-dispatch-fn)
The implementation:
(ns dbbackend.mysql
(:requires dbbackend))
(defmethod query com.mysql.jdbc.Connection
[c q]
...)
(defmethod persist! com.mysql.jdbc.Connection
[c o]
...)
The usage:
(ns user
(:require dbbackend dbbackend.mysql))
(def mysql-connection (connect-to-mysql))
(query mysql-connection some-query)
You can find a real-world example of this approach under the hood of ClojureQL.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With