Skip to content

Instantly share code, notes, and snippets.

@runexec
Last active June 17, 2020 03:47
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save runexec/786b92b97b5a3a0ffac7 to your computer and use it in GitHub Desktop.
Save runexec/786b92b97b5a3a0ffac7 to your computer and use it in GitHub Desktop.
Clojure Does Objects Better

Clojure does Objects Better

A hopefully short and concise explanation as to how Clojure deals with Objects. If you already write Clojure, this isn't for you.

You know what an Interface is if you write/read Java or PHP 5+. In Clojure it might be called defprotocol.

user> (defprotocol IABC
        (also-oo [this])
        (another-fn [this x]))
IABC

You might be tempted to use the classic implements or extends keywords when defining a new Class. In Clojure it might be called defrecord.

user> (defrecord ABC [name]
        ;; You can implement multiple Classes here
        IABC
        (also-oo [this]
          (str "This => " (:name this)))
        (another-fn [this x]
          (format "%s says: %s" (:name this) x)))
user.ABC

You might be tempted to use the classic new keyword to instantiate a new object. In Clojure it might be a simple decimal suffix added to the Class name so that it can be used as a predicate.

user> (def a (ABC. "Roger"))
#'user/a
user> (another-fn a "oo programing")
"Roger says: oo programing"
user> (also-oo a)
"This => Roger"

Clojure can just do it better.

user> (defmulti this-is-oo type)
nil
user> (defmethod this-is-oo ABC [this]
        (println (:name this)))
#<MultiFn clojure.lang.MultiFn@8625d0e>
user> (this-is-oo a)
Roger
user> (defmethod this-is-oo java.lang.String [this]
        "Got a string and not ABC!!!!")
#<MultiFn clojure.lang.MultiFn@8625d0e>
user> (this-is-oo "a")
"Got a string and not ABC!!!!"
@nilliams
Copy link

This is interesting but I feel like I'm missing something. I'd love to hear more discussion/expansion on this as someone who bought the Clojure book, loves Rich Hickey's talks and yet was completely turned off by the language.

I am unconvinced by the first 3 examples as they are in my opinion significantly less readable than the equivalent code in a C-style, OOP-friendly language such as Ruby or Python with basic Class syntax.*

Please could you elaborate on the final example as to what you are doing and why it is better.

*I realise readability is subjective and Lispers consciously trade-off 'syntax' (which I tend to believe is necessary for expressiveness & therefore readability) for the benefits of homoiconicity. I'm yet to understand that trade-off as to me homoiconicity offers no practical benefit in the day-to-day, team-building-an-app environment.

@lkrubner
Copy link

I find that I never use protocols or interfaces in Clojure, because most of the times I would use such things, if this were an object oriented language, I instead use multi-methods. This might be the point that runexec was making, as they write "Clojure can just do it better."

The key issue is polymorphism. All of us want to work in a language that gives us powerful forms of polymorphism. All of the major languages offer us some types of polymorphism, but let us ask, which gives us the most powerful and flexible forms? Myles Megyesi wrote a great article on this:

http://blog.8thlight.com/myles-megyesi/2012/04/26/polymorphism-in-clojure.html

2 common forms of polymorphism in Clojure, which Megyesi mentions, are:

1.) multi-methods

2.) functions as parameters

If you really like the object oriented style, you can imitate that style in Clojure by combining a record (a struct) with a multi-method, or you could just have a multi-method whose dispatch function checks and enforces a particular contract, or set of constraints (this is the style that I like best).

The crucial point to get is that multi-methods are the most powerful and flexible form of polymorphism that you can use. If you'd like to use a them in a traditional object oriented style, then you can match against the class or type of the arguments given to the dispatch function, but if you'd like to go beyond the traditional object oriented style, then you can match against any aspect of the arguments being given to the dispatch function.

@spronkey
Copy link

@nilliams I've been battling with these same thoughts for a while now. The example above does little to highlight the benefits(?) of Clojure to someone who isn't very familiar with it in the first place.

@adambard
Copy link

@lkrubner Multimethods are indeed great, but I tend use Protocols when I want to group a set of functionality together. Here's a pattern I tend to use a lot:

(defprotocol UserStore
  (get-user [user-id])
  (put-user! [user]))

(extend-protocol UserStore

  PostgresStore
  (get-user [user-id] ...implementation)
  (put-user! [user] ...implementation)

  AtomStore
  (get-user [user-id] ...implementation)
  (put-user! [user] ...implementation))

Although either is acceptable, to me, this has less cruft than:

(defmulti get-user type)
(defmulti put-user type)

(defmethod get-user PostgresStore [user-id]
  ...implementation)
(defmethod put-user! PostgresStore [user]
  ...implementation)

(defmethod get-user AtomStore [user-id]
  ...implementation)
(defmethod put-user! AtomStore [user]
  ...implementation)

@andrewchambers
Copy link

@nilliams

You are confusing what you are used to seeing, with what is readable. I'm not used to reading chinese, so I assume its less intuitive than english.

@davelnewton
Copy link

@nilliams

[...] homoiconicity offers no practical benefit in the day-to-day, team-building-an-app environment

I'd argue that has more to do with (a) the apps you write, and/or (b) how you write them.

If you're not using anything macro-like then it's likely not a benefit. If you don't need highly-malleable functionality it may not be a benefit, but in my experience even a light layer of macros can vastly improve readability and communication.

Over the years it still seems to me that until you actually need and/or use it it will never be obvious why this is a good thing. This is partially because it's difficult to externalize what strikes Lisp-y people as obvious. Personally I advocate using the functionality until it's difficult to live without it--then the benefits are clear.

(Macros can be horribly, terribly abused, of course, perhaps to a greater degree than any other language feature. That's not a language issue, but one of discipline, documentation, and system-/environment-level knowledge.)

@nilliams
Copy link

nilliams commented Dec 4, 2014

@andrewchamer I'm not confusing readability with familiarity. I put myself in a camp that believes that code syntax like class makes code more expressive and I take my definition of readability from how well the code expresses its intention.

@davelnewton I've never met a programmer who wrote macros I've wanted to use. I'm yet to come across a macro in the Clojure book I want to use. Macros as far as I can tell have been considered a bad idea in the C world and C-style language worlds for a very long time, because if any one macro offered benefit enough that it warranted inflicting that macro on your teammates, it'd be added to the language.

Every now and then a framework in a non-macro-supporting language I use or am interested in proposes something that could likely be created with macros that seems genuinely useful, but that is really once in a blue moon. As a modern example I'm thinking of something like the annotations the Angular.js team are adding by creating AtScript for Angular 2.0.

Yet still I'd rather use their invented superset language and deal with all the drawbacks that brings abstraction-wise and tooling-wise than move to a language that completely sacrifices expressive syntax in the name of homoiconicity and reads like an AST.

@davelnewton I realise I've wandered off on a rant here - I guess I'm saying I don't believe that 'even a light layer of macros can vastly improve readability and communication', and even if I did I think I'd rather see those macros applied to a language with more syntax for day-to-day programming than Clojure offers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment