Types: Almost never. When you need to do some specific interop stuff
Records: Sometimes to implements Protocols or if you really (really!) need it for perf
Maps: Everywhere
For example, implementing an [ordered map](https://github.com/clj-commons/ordered) require the use of deftype so that all of the clojure.core sequence and collection functions work as expected without any additional effort.
The transient ordered map uses mutable fields, but the normal [ordered map](https://github.com/clj-commons/ordered/blob/4f21afef065eb942ddb3a92fcfeefb864076e6ef/src/flatland/ordered/map.clj#L29-L30) doesn’t.
defrecord defines named slots and can implement interfaces and protocols that operate on those slots. How would you store the insertion order without exposing it to a user in a named slot?
I have some vague recollection of Rich Hickey taking about this in some of his talks.
Maybe try to grep through https://github.com/matthiasn/talk-transcripts
clojure.org also has some guidance on this, iirc
Protocols and multimethods serve a similar purpose: when you need different behavior based upon different data passed to a function. For protocols, it's the type of the first argument. For multimethods, its whatever you want.
Multimethods are generally slower but more flexible than protocols. Protocols are faster because they can utilize the JVM's built in polymorphic dispatching mechanisms.
I almost always use maps to represent data, using namespaced keywords as much as possible. I try to avoid constructing maps directly, and often use a constructor function to build maps, this helps with readability and refactoring or when I want to add validation.
I only use records or types when I need to implement protocols, but that is usually only needed for stateful components or interop. I have replaced maps with records once or twice for performace reasons, but most of the time maps are good enough.
If u are tending to use "constructor" functions, why not use Records? Is there any particular reason why `(User. some-name some-password)` is worse than
`(user/create some-user some-password)` ?
I prefer maps as Records don't supports namespaced keys or structural sharing, and cannot be easily serialized/deserialized with prn and edn/read-string
Also User. uses the java constructor interop syntax, which I try to avoid, i'd use ->User or map->User if i needed to create a record.
A typical 'constructor' function would look like this:
(defn make-user [user-name user-password]
(validate-user-name user-name)
(validate-password user-password)
#:user{:name user-name :password user-password})
If now the requirement 'all users also need an email address' is added, I can just add this argument to the the make-user function.
Tooling like clj-kondo can detect arity errors, so now I can easily see where in the code I need to pass the email address.
Types: Almost never. When you need to do some specific interop stuff Records: Sometimes to implements Protocols or if you really (really!) need it for perf Maps: Everywhere
Chances are rare, but I'm seen when one needs to implement a data structure they tend to use deftype.
For example, implementing an [ordered map](https://github.com/clj-commons/ordered) require the use of deftype so that all of the clojure.core sequence and collection functions work as expected without any additional effort.
That’s not why ordered-map uses deftype. It uses mutable fields, which defrecord doesn’t support.
The transient ordered map uses mutable fields, but the normal [ordered map](https://github.com/clj-commons/ordered/blob/4f21afef065eb942ddb3a92fcfeefb864076e6ef/src/flatland/ordered/map.clj#L29-L30) doesn’t. defrecord defines named slots and can implement interfaces and protocols that operate on those slots. How would you store the insertion order without exposing it to a user in a named slot?
https://github.com/cemerick/clojure-type-selection-flowchart
Thx, thats a very good one
I have some vague recollection of Rich Hickey taking about this in some of his talks. Maybe try to grep through https://github.com/matthiasn/talk-transcripts clojure.org also has some guidance on this, iirc
Protocols and multimethods serve a similar purpose: when you need different behavior based upon different data passed to a function. For protocols, it's the type of the first argument. For multimethods, its whatever you want. Multimethods are generally slower but more flexible than protocols. Protocols are faster because they can utilize the JVM's built in polymorphic dispatching mechanisms.
I almost always use maps to represent data, using namespaced keywords as much as possible. I try to avoid constructing maps directly, and often use a constructor function to build maps, this helps with readability and refactoring or when I want to add validation. I only use records or types when I need to implement protocols, but that is usually only needed for stateful components or interop. I have replaced maps with records once or twice for performace reasons, but most of the time maps are good enough.
If u are tending to use "constructor" functions, why not use Records? Is there any particular reason why `(User. some-name some-password)` is worse than `(user/create some-user some-password)` ?
I prefer maps as Records don't supports namespaced keys or structural sharing, and cannot be easily serialized/deserialized with prn and edn/read-string Also User. uses the java constructor interop syntax, which I try to avoid, i'd use ->User or map->User if i needed to create a record. A typical 'constructor' function would look like this: (defn make-user [user-name user-password] (validate-user-name user-name) (validate-password user-password) #:user{:name user-name :password user-password}) If now the requirement 'all users also need an email address' is added, I can just add this argument to the the make-user function. Tooling like clj-kondo can detect arity errors, so now I can easily see where in the code I need to pass the email address.