Methods in Clojure

Clojure supports functions defined on record types, which are similar to methods on structs in other languages.

(ns methods-example
  (:require [clojure.string :as str]))

(defrecord Rectangle [width height])

;; This area function takes a Rectangle as its first argument
(defn area [^Rectangle r]
  (* (:width r) (:height r)))

;; Here's another function that operates on a Rectangle
(defn perim [^Rectangle r]
  (+ (* 2 (:width r)) (* 2 (:height r))))

(defn -main []
  (let [r (->Rectangle 10 5)]
    ;; Here we call the two functions defined for our record
    (println "area: " (area r))
    (println "perim:" (perim r))

    ;; In Clojure, we don't need to worry about pointers vs values
    ;; All data structures are immutable by default
    (println "area: " (area r))
    (println "perim:" (perim r))))

;; Run the main function
(-main)

To run this program, save it as methods_example.clj and use the Clojure command-line tool:

$ clj methods_example.clj
area:  50
perim: 30
area:  50
perim: 30

In this Clojure example, we define a Rectangle record type using defrecord. Instead of methods, we define functions that take the record as their first argument. This is a common pattern in Clojure and other functional programming languages.

The area and perim functions are defined outside the record, but they operate on Rectangle instances. This is similar to how methods work in other languages, but with a more explicit passing of the record as an argument.

In the -main function, we create a Rectangle instance and call our functions on it. Clojure doesn’t have the concept of pointers vs values in the same way as some other languages. All data structures in Clojure are immutable by default, so we don’t need to worry about whether we’re passing a reference or a value.

Next, we’ll look at Clojure’s mechanism for defining abstract behaviors: protocols and multimethods.