clojure / clojure/clojure-site
Protocols implementation affordances using defrecord/deftype vs. extend-protocol
Nobody has claimed this yet.
- Dominant language
- HTML
- Stars
- 259
- Forks
- 275
- PR merge metrics
- No merged PRs in 30d
Description
When implementing Protocols using defrecord there are some missing affordances.
As an example, :pre and :post conditions cannot be applied.
[ Yes, I know we will soon have spec but these affordances will not be deprecated AFAIK. ]
Affordances not available on defrecord or deftype
(defprotocol Squarer (square [x]))
(defrecord PosIntSquarer [x]
Squarer
(square [_] (* x x)))
; Usage
(defprotocol Squarer (square [x]))
;=> Squarer
(defrecord PosIntSquarer [x]
Squarer
(square [_] (* x x)))
;=> practice1.core.PosIntSquarer
(square (->PosIntSquarer 2))
;=> 4
;---^^^ All good
(square (->PosIntSquarer "A"))
;CompilerException java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number, compiling:(.../core.clj:162:1)
;---^^^ We would like to prevent this using a simple assertion
(defrecord PosIntSquarer [x]
Squarer
(square [_]
{:pre [(pos-int? x)]}
(* x x)))
;=> practice1.core.PosIntSquarer
(square (->PosIntSquarer "A"))
;CompilerException java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number, compiling:(.../core.clj:162:1)
;---^^^ The pre-condition is being ignored
(defrecord PosIntSquarer [x]
Squarer
(square [_]
{:pre [(pos-int? x)]
:post [(pos? %)]}
(* x x)))
CompilerException java.lang.RuntimeException: Unable to resolve symbol: % in this context, compiling:(.../core.clj:158:13)
;---^^^ The post condition compilation fail shows that all affordances are not available
; Calling externally declared functions is one solution
(defn positive-square [x]
{:pre [(pos-int? x)]
:post [(pos? %)]}
(* x x))
;=> #'practice1.core/positive-square
(defrecord PosIntSquarer [x]
Squarer
(square [_]
(positive-square x)))
;=> practice1.core.PosIntSquarer
(square (->PosIntSquarer "A"))
;CompilerException java.lang.AssertionError: Assert failed: (pos-int? x), compiling:(.../core.clj:163:1)
Affordances are available via extend-protocol
(defrecord DirectSquarer [x])
=> practice1.core.DirectSquarer
(extend-protocol Squarer
DirectSquarer
(square [this]
{:pre [(pos-int? (:x this))]
:post [(pos? %)]}
(* (:x this) (:x this))))
;=> nil
(square (->PosIntSquarer 2))
;=> 4
(square (->PosIntSquarer "A"))
;CompilerException java.lang.AssertionError: Assert failed: (pos-int? x), compiling:(.../core.clj:178:1)
(square (->PosIntSquarer -2))
;CompilerException java.lang.AssertionError: Assert failed: (pos-int? x), compiling:(.../core.clj:176:1)
I don't want to claim this is a bug. But it's a sign that these options have pros and cons which are not currently well explained.
I would like to develop a more comprehensive table of features / affordances might be nicer than the current bullet list
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Review the current bullet list about protocol implementation options and compare the affordances shown in the issue's defrecord/deftype and extend-protocol examples. Document the differences in a comprehensive feature table, including pre- and post-condition behavior, so the trade-offs are clearer.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- clojure
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100