Interfaces in Clojure

In Clojure, we don’t have interfaces in the same way as Go, but we can use protocols to achieve similar functionality. Here’s how we can implement the geometric shapes example:

(ns geometry
  (:require [clojure.math :as math]))

;; Define a protocol for geometric shapes
(defprotocol Geometry
  (area [shape])
  (perim [shape]))

;; Implement the protocol for rectangles
(defrecord Rectangle [width height]
  Geometry
  (area [this] (* (:width this) (:height this)))
  (perim [this] (* 2 (+ (:width this) (:height this)))))

;; Implement the protocol for circles
(defrecord Circle [radius]
  Geometry
  (area [this] (* Math/PI (:radius this) (:radius this)))
  (perim [this] (* 2 Math/PI (:radius this))))

;; A function to measure any shape that implements the Geometry protocol
(defn measure [shape]
  (println shape)
  (println (area shape))
  (println (perim shape)))

(defn -main []
  (let [r (->Rectangle 3 4)
        c (->Circle 5)]
    (measure r)
    (measure c)))

In this Clojure code:

  1. We define a Geometry protocol with area and perim methods, which is similar to the geometry interface in the original code.

  2. We use defrecord to define Rectangle and Circle types, which implement the Geometry protocol. This is analogous to defining structs and implementing methods for them in Go.

  3. The measure function takes any shape that implements the Geometry protocol, demonstrating polymorphism.

  4. In the -main function, we create instances of Rectangle and Circle and call measure on them.

To run this program:

$ clj -M geometry.clj
#geometry.Rectangle{:width 3, :height 4}
12
14
#geometry.Circle{:radius 5}
78.53981633974483
31.41592653589793

This Clojure implementation achieves the same functionality as the original Go code, using protocols instead of interfaces, and records instead of structs. The measure function demonstrates how we can work with any shape that implements the Geometry protocol, showcasing Clojure’s polymorphism capabilities.

Clojure’s protocols provide a flexible way to define abstract behaviors that can be implemented by different types, similar to interfaces in other languages. This allows for polymorphic behavior while maintaining the benefits of Clojure’s functional programming paradigm.