Struct Embedding in Clojure
Clojure supports the concept of protocols and records, which can be used to achieve composition similar to struct embedding in other languages. This example demonstrates how to use these features to create a similar structure and behavior.
(ns struct-embedding
(:require [clojure.string :as str]))
(defprotocol Describer
(describe [this]))
(defrecord Base [num]
Describer
(describe [this]
(str "base with num=" (:num this))))
(defrecord Container [base str]
Describer
(describe [this]
(describe (:base this))))
(defn -main []
(let [co (->Container (->Base 1) "some name")]
; We can access the base's fields directly on co
(println (str "co={num: " (-> co :base :num) ", str: " (:str co) "}"))
; Alternatively, we can spell out the full path using the embedded type name
(println "also num:" (-> co :base :num))
; Since Container includes Base, we can call describe on Container
(println "describe:" (describe co))
; We can use the Describer protocol to demonstrate interface-like behavior
(let [d co]
(println "describer:" (describe d)))))
(-main)
When creating records with literals, we initialize the embedding explicitly. In this case, we create a Base
instance and pass it to the Container
constructor.
The Container
record includes a Base
instance, allowing us to access its fields through the Container
. We can also directly invoke methods (in this case, the describe
function) that were defined for Base
on a Container
instance.
The Describer
protocol is used to define a common interface, which both Base
and Container
implement. This demonstrates how Clojure can achieve behavior similar to interface implementation through embedding.
To run this program, save it as struct_embedding.clj
and use:
$ clojure struct_embedding.clj
co={num: 1, str: some name}
also num: 1
describe: base with num=1
describer: base with num=1
This example showcases how Clojure can use records and protocols to achieve composition and interface-like behavior, similar to struct embedding in other languages.