Clojure Protocols

I have less than a week’s experience on Clojure protocols. But I know enough to say they are pretty interesting, especially if you are working with an existing Java code base. This is all nicely documented in Clojure In Action Chapter 14 . Chapter 14 builds the case for why you would want protocols, because for working with Java classes, they are simpler to define and use.

So, here are some samples:

Account.java (for a simplified banking object)


import java.util.Date;

public class Account {

public Account( int in_acct_num,
                char in_acct_type,

                double in_acct_int_val)
                {
                  acct_num = in_acct_num;
                  acct_type = in_acct_type;
                  acct_int_val = in_acct_int_val;
                  acct_age_days = 0;
                  cur_bal = 500.00;
                 }

public int getAcctNum () {return acct_num;}

public char getAcctType () {return acct_type;}
public double getAcctInt() {return acct_int_val;}

public double getTransAmt() {return trans_amt;}
public void   setTransAmt(double in_trans_amt) {trans_amt = in_trans_amt;
                                                acct_age_days++;}

public double getCurBal() {return cur_bal;}
public void setCurBal(double in_cur_bal) {cur_bal = in_cur_bal;
acct_age_days++;}

public int getAcctAgeDays () {return acct_age_days;}

public void printAcctInfo()  {
                              System.out.format("%s", "Acct #: Balance\n");
                              System.out.format("%d %6.2f%n ", getAcctNum(), getCurBal());
                             }

private int acct_num;
private char acct_type;
private double acct_int_val;
private double trans_amt;
private double cur_bal;
private int  acct_age_days;
private String diag_info;
}

A Clojure code snippet to handle Account.java and Clojure defrecords:


(defrecord acct-info [acct-num acct-type int-val trans-amt cur-bal acct-age-days])

; protocols section 1/7/2012
(defprotocol AccountInfo
"Protocol for accessing interest calculations"
(get-acct-num [a])
(get-acct-int [a])
(get-trans-amt [a])
(set-trans-amt [a amt])
(get-cur-bal [a])
(set-cur-bal [a amt])
(get-acct-age-days [a])
(print-acct-info [a]))

(extend-protocol AccountInfo
clojure.lang.IPersistentMap
   (get-acct-num [a]
     (:acct-num a))
   (get-acct-int [a]
     (:int-val a))
   (get-acct-trans-amt [a]
     (:trans-amt a))
   (set-acct-trans-amt [a amt]
     (assoc a :trans-amt amt))
   (get-cur-bal [a]
     (:cur-bal a))
   (set-cur-bal [a amt]
     (assoc a :cur-bal amt))
   (get-acct-age-days [a]
     (:acct-age-days a))

   (print-acct-info [a]
     (println (:acct-num a) (:cur-bal a))))

(extend-protocol AccountInfo
   Account
   (get-acct-num [a]
     (.getAcctNum a))

   (get-acct-type [a]
     (.getAcctType a))

   (get-acct-int [a]
     (.getAcctInt a))

   (get-trans-amt [a]
     (.getTransAmt a))

   (set-trans-amt [a amt]
     (.setTransAmt a amt))

   (get-cur-bal [a]
     (.getCurBal a))

   (set-cur-bal [a amt]
     (.setCurBal a amt))

   (get-acct-age-days [a]
     (.getAcctAgeDays a))

   (print-acct-info [a]
     (.printAcctInfo a)))

And finally the Clojure test module core.clj


(ns bank-3.test.core
   (:use [bank-3.core])
   (:import Account)
   (:use [clojure.test]))

(defrecord acct-info [acct-num acct-type int-val trans-amt cur-bal acct-age-days])

(deftest test-acct-info
   (let [tst-acct-m1 (acct-info. 1000 C 0.02 0.00 0.0 0)
         tst-acct-j1 (Account. 2000 C 0.02)
         tst-acct-m2 (acct-info. 1005 S 0.04 10.00 0.0 0)
         tst-acct-j2 (Account. 2010 M 0.06) ]
         (let [new-tst-acct-m1 (set-cur-bal tst-acct-m1 100.00)]
           (set-cur-bal tst-acct-j1 100.00)
           (is (not (= (get-acct-num new-tst-acct-m1) (get-acct-num tst-acct-j1))))
           (is (= (get-cur-bal new-tst-acct-m1) (get-cur-bal tst-acct-j1)))
           (is (= (get-acct-int new-tst-acct-m1) (get-acct-int tst-acct-j1))))))
Advertisements

4 Comments

Filed under Clojure

4 responses to “Clojure Protocols

  1. Protocols are cool but I’m not sure this is the best example. Your setter methods do different things depending on concrete type – for the java data structure, it mutates internal state and returns nothing, but for the defrecord it returns a new record but leaves the original intact. There isn’t one unified contract for the protocol.

  2. nice post, thanks for sharing. I also noticed that you are naming parameter in java like Clojure way ;).

    • Octopusgrabbus

      Other than a couple of years of sustaining engineering on a Java app for Co-Standby for Windows, I’m really not a Java programmer. Thanks for pointing that out.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s