FWIW before I came to Clojure I did a lot of Erlang and in the beginning I was at the exact same spot wanting to use pattern matching everywhere because it is so damn cool. Same goes for tagged literals.
After a little while I realized that it is just not the way to do it in Clojure and forcing the Erlang(or Elixir)-Way onto Clojure is not ideal and probably overthinking it. Protocols provide a good/better solution to some problems and using Clojure's excellent handling&support of "data" solves the rest. While it might feel weird in the beginning, wait a while for Clojure to click and you probably won't even miss pattern matching. Looking at the "(defn register [...])" example. Where is the problem with the first solution? It doesn't have the bugs the other implementations have and is extremely simple to reason about? The other two solutions do the exact same thing just slower with absolutely no gain. If you need the "status" abstraction use a real state machine. Don't write a recursive function when you don't have to. The code should never be at a point where it can be called with forged data and directly skip over the :create and :check-unqiue states which is possible in 2 of the 3 solutions. Embrace the data and data all the things is all I have to say. Also if you like types (using prismatic/schema [1], core.typed looks pretty similar): (def ContactId s/Int) (s/defrecord Placeholder [text :- s/Str]) (s/defrecord Existing [contact-id :- ContactId]) (s/defrecord OneOff [name :- s/Str email :- s/Str]) (def Recipient (s/either PlaceHolder Existing OneOff)) Just my 2 cents, /thomas [1] https://github.com/Prismatic/schema > > Possible F# (might not compipe!!) > > Recipient = | Placeholder of string > | Existing of ContactId > | OneoffRecipient of string * string > > > Possible options in Clojure > > {:type :placeholder ; or :existing :one-off > :placeholder-text :all-my-team-mates > :name nil > :email nil > :contact-id nil > } > > ;; or > > (defrecord PlaceholdRecipient [placeholder-text]) > (defrecord ExistingReceipient [contact-id]) > (defrecord OneoffReceipient [name email]) > > ;; or > > [:one-off name email] > [:existing contact-id] > [:placeholder text] > ;; [:self] > > The variant needed not be a two element vector. It is a 1 or more element > vector. We could just as easily have a fourth variant [:self] with no data > to indicate send message to self. > > To actually use the above data structures and get the actual email > addresses from them, we need atleast three methods - 1) fetch the emails of > all the users team mates, 2) fetch the email for a contact in the > datastore, 3) return the email as is. We could implement these using a > multi method or implement some protocol or use a pattern matched method. > Each choice has a different extensibility story and are all equally valid. > We don't always replace every hashmap with a record. Variants are a viable > alternative over using a map like the one shown in the first Clojure > choice. > > >> As much as possible I try to build my apps in such a way that the > program can self-explore the data you give it to self-optimize, > self-extend, or otherwise provide flexibility to the programmer. You can't > do that with a variant as a vector. Hand a vector to a function and the > logic has to be something like "If this is a two element vector and the > first thing is a keyword, then this might be a variant, but I'm not sure > because it could also just be a vector". While if you pass in a record, > type, or even a variant type, it's trivial to have a program recognize that > value and act upon it. > > I have no experience writing apps that had to operate on any arbitrary > data with any arbitrary structure. Pretty much all the functions in my apps > could rely on data arriving in a particular format in a particular order > (positional destructuring). > > More importantly we are pretty much agreeing that [:tagged vectors with > some data] are only a representation of a variant. One chosen out of > convenience and existing feature set. We could have a defrecord Variant > with two fields, a type keyword and value hashmap. If core.match was able > to easily pattern match over a record as well as easily destructure the > values in the value hashmap, we would use it. From what I have seen of > core.match, it is nowhere near as easy/clean as pattern matching a vector. > > >> As far as performance goes, this is normally the sort of thing that > gets baked into an app at a pretty low level, that's why I suggest it > should be as fast as possible. If you're going to be processing millions of > these things during the life of an app, and transforming them from one > format to another, a little tweaking here and there may save you some pain > in the end. > > Pattern matching itself is too slow for it to be viable in any performance > sensitive context. That said, I feel optimize first for > readability/maintainability, if profiling shows a bottleneck, then and only > then optimize that area for performance. > > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.