Interfaces and Methods
Defining an Interface
definterface declares a Go interface — a set of methods a type must implement:
(definterface Shape
(Area [] -> float64)
(Describe [] -> string))
Implementing Methods
defmethod attaches a method to a struct. The first parameter is the receiver:
(defstruct Circle
radius float64)
(defmethod Circle Area [c] -> float64
(* math/pi (:radius c) (:radius c)))
(defmethod Circle Describe [c] -> string
(fmt/sprintf "circle(r=%.2f)" (:radius c)))
(defstruct Rect
width float64
height float64)
(defmethod Rect Area [r] -> float64
(* (:width r) (:height r)))
(defmethod Rect Describe [r] -> string
(fmt/sprintf "rect(%.2f × %.2f)" (:width r) (:height r)))
Calling Methods
Methods are called as regular functions — the receiver is just the first argument. The compiler routes the call to the right method:
(area c) ; → c.Area()
(describe c) ; → c.Describe()
This is dot-free dispatch. No (.Area c) syntax needed for your own methods.
Using the Interface
Functions that accept an interface work with any implementing type:
(defn print-shape [s Shape] -> void
(fmt/printf "%s: area = %.4f\n" (describe s) (area s)))
(defn main [] -> void
(let [shapes [
(Circle. {:radius 5.0})
(Rect. {:width 4.0 :height 6.0})]]
(doseq [s shapes]
(print-shape s))))
Output:
circle(r=5.00): area = 78.5398
rect(4.00 × 6.00): area = 24.0000
Methods on Named Types
defmethod works on deftype too:
(deftype Celsius float64)
(deftype Fahrenheit float64)
(defmethod Celsius ToF [c] -> Fahrenheit
(Fahrenheit (+ (* (float64 c) 9.0 / 5.0) 32.0)))
(let [c (Celsius 100.0)]
(to-f c)) ; Fahrenheit(212.0)
Full Example
(ns main)
(definterface Shape
(Area [] -> float64)
(Describe [] -> string))
(defstruct Circle radius float64)
(defstruct Rect width float64 height float64)
(defstruct Triangle a float64 b float64 c float64)
(defmethod Circle Area [c] -> float64
(* math/pi (:radius c) (:radius c)))
(defmethod Circle Describe [c] -> string
(fmt/sprintf "circle(r=%.2f)" (:radius c)))
(defmethod Rect Area [r] -> float64
(* (:width r) (:height r)))
(defmethod Rect Describe [r] -> string
(fmt/sprintf "rect(%.2f x %.2f)" (:width r) (:height r)))
(defmethod Triangle Area [t] -> float64
(let [s (* 0.5 (+ (:a t) (:b t) (:c t)))]
(math/sqrt (* s (- s (:a t)) (- s (:b t)) (- s (:c t))))))
(defmethod Triangle Describe [t] -> string
(fmt/sprintf "triangle(%.2f, %.2f, %.2f)" (:a t) (:b t) (:c t)))
(defn total-area [shapes []Shape] -> float64
(reduce (fn [sum s] (+ sum (area s))) 0.0 shapes))
(defn main [] -> void
(let [shapes []Shape [
(Circle. {:radius 5.0})
(Rect. {:width 4.0 :height 6.0})
(Triangle. {:a 3.0 :b 4.0 :c 5.0})]]
(doseq [s shapes]
(fmt/printf " %-26s area = %.4f\n" (describe s) (area s)))
(println "total area:" (total-area shapes))))