Michael Bylstra
Michael Bylstra

Reputation: 5232

How can you validate function arguments in an efficient and DRY manner?

Let’s say I have three functions that operate on matrices:

(defn flip [matrix] (...))
(defn rotate [matrix] (...))
(defn inc-all [matrix] (...))

Imagine each function requires a vector of vectors of ints (where each inner vector is the same length) in order to function correctly.

I could provide a an assert-matrix function that validates that the matrix data is in the correct format:

(defn assert-matrix [matrix] (...) )

However, the flip function (for example) has no way of knowing whether data is passed to the function has been validated (it is totally up to the user whether they could be bothered validating it before passing it to the function). Therefore, to guarantee correctness flip would need to defined as:

(defn flip [matrix]
    (assert-matrix matrix)
    (...))

There are two main problems here:

In an Object Oriented language, I’d create a Class named Matrix with a constructor that checks the validity of the constructor args when the instance is created. There’s no need for methods to re-check the validity as they can be confident the data was validated when the class was initialised.

How would this be achieved in Clojure?

Upvotes: 1

Views: 167

Answers (2)

Ankur
Ankur

Reputation: 33657

You could use a protocol to represent all the operations on matrix and then create a function that acts like the "constructor" for matrix:

(defprotocol IMatrix                                                                                                                                 
  (m-flip [_])                                                                                                                                       
  (m-rotate [_])                                                                                                                                     
  (m-vals [_]))                                                                                                                                      

(defn create-matrix [& rows]                                                                                                                         
  (if (apply distinct? (map count rows))                                                                                                             
    (throw (Exception. "Voila, what are you doing man"))                                                                                             
    (reify                                                                                                                                           
      IMatrix                                                                                                                                        
      (m-flip [_] (create-matrix rows))                                                                                                              
      (m-rotate [_] (create-matrix rows))                                                                                                            
      (m-vals [_] (vec rows))))) 



(def m (create-matrix [1 2 3] [4 5 6]))                                                                                                              
(m-flip m)

Upvotes: 2

skuro
skuro

Reputation: 13514

There are several ways to validate a data structure only once, you could for instance write a with-matrix macro along the lines of the following:

(defmacro -m> [matrix & forms]
  `(do
    (assert-matrix ~matrix
      (-> ~matrix
        ~@forms))

which would allow you to do:

(-m> matrix flip rotate)

The above extends the threading macro to better cope with your use case.

There can be infinite variations of the same approach, but the idea should still be the same: the macro will make sure that a piece of code is executed only if the validation succeeds, with functions operating on matrices without any embedded validation code. Instead of once per method execution, the validation will be executed once per code block.

Another way could be to make sure all the code paths to matrix functions have a validation boundary somewhere.

You may also want to check out trammel.

Upvotes: 2

Related Questions