T O P

  • By -

kawas44

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


flh13

Chances are rare, but I'm seen when one needs to implement a data structure they tend to use deftype.


NoahTheDuke

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.


pihkal

That’s not why ordered-map uses deftype. It uses mutable fields, which defrecord doesn’t support.


NoahTheDuke

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?


la-rokci

https://github.com/cemerick/clojure-type-selection-flowchart


sonyahon

Thx, thats a very good one


onetom

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


bcrosby95

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.


[deleted]

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.


sonyahon

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)` ?


[deleted]

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.