In my mind you are not leaving OO behind when you get into Clojure, but the big conceptual challenges revolve around doing things in an immutable way. It is obvious how to do some things and not to do others.
which elaborates on SOLID OO design in Clojure and provides some code examples. For example there's a pretty clear example of the right way to apply SRP to a functional paradigm.
I'd interpret it as you need SOLID to do successful OO (required but not sufficient), but using as much SOLID as reasonably possible under any paradigm also happens to result in good/better software. In that way there can be a lot of confusion about what is OO and what is merely usually found nearby OO but isn't OO specific.
OO is such an overloaded term, it's not possible to really draw the line in any concrete way. Much in the way that "functional programming" tends to mean different things to different people... but OO is even more varied in its actual meaning, in general.
They are also used by Dylan, the Lisp with Algol like syntax developed by Apple,
Protocols provide the same type of polymorphism offered by Objective-C protocols, Java/C# interfaces, Go interfaces, ...
Many mainstream developers might only know one way of doing OO, but back in the day we could choose between Smalltalk, Lisp, Beta, Eiffel, Sather, C++, Modula-3, Oberon, Component Pascal, SELF, .....
Each had their own view how encapsulation, polymorphism, message dispatch, type extensions should take place.
So it is kind of funny to have some in FP languages bashing OO, while successful FP languages are actually hybrid. At the same time having people in teh OO side bashing FP, while their languages keep absorbing FP concepts.
Eventually we will get back to Smalltalk and Lisp, which already provided both ways in the late 70's.
But protocols and mulitmethods are different from OO in the sense that the functions are decoupled from the state. You don't store state in a protocol, you just define an interface. That's pretty different from the way most people think about OO in c++, java, swift, objective-c, etc. In Clojure, you have Records and maps, which hold your "state" or your values, and you have protocols which define your functions, and the two are isolated from each other and not attached in any way. That's quite different from OO in general, don't you think?
Just go read the Xerox PARC papers on how to do OO in Lisp, for example.
Back when OO was new there were multiple ways of how to do OO.
Some languages used the Smalltalk approach.
Others took the Simula approach where objects were an evolution of modules that could be extend and manipulated.
And there were lots of other options scattered around OOPSLA papers.
What happens is that there are now a couple of generations of new developers that didn't live through the procedural to object oriented programming revolution, nor were doing their CS degree in those days, so many understand OO as C++, Java et al do it and think no other way is possible.
The way Lisp does it, is quite common in the OO languages that offer multi-dispatch in method binding.
Since all method arguments types are used in the method resolution, it doesn't make sense to bind the methods to a specific object.
LOOPS and CLOS books/papers are pretty clear that they are doing OOP.
> OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I'm not aware of them. [1]
You can do this easily with multimethods, in fact they allow for significantly "later" binding than traditional OO languages like Java or C++.
multi-methods at least are isa?-based. This implies that they obey ad-hoc hierarchies created via derive as well as traditional java inheritance hierarchies.
protocols are little more than open-ended interfaces (i.e. I can extend them at run-time to my things and to other things).
Just a slight addendum: Clojure multimethods can resolve to a concrete method implementation based on any function of their parameters.
So, in addition to single dispatch based on class (a la Java), you could also dispatch based on the classes of multiple parameters or on the value of the field 3 objects deep.
kinda. The dispatch is on a single value and isa? isn't mapped over that single value if it happens to be a collection. This means you can dispatch on multiple concrete values in a collection or the isa? hierarchies of a single thing, but you don't get to isa? everything in the collection (unless you do it yourself over some limited set of things you care about; like you said, method dispatch is over ANY function).
This means that you can do something like
(defmulti cares-about-a-and-c
"multimethod that cares about the first and third args"
(fn [a b c] [a c]))
(defmethod cares-about-a-and-c [:alpha :gamma]
[a b c]
(prn "got :alpha and :gamma"))
(defmethod cares-about-a-and-c [1 3]
[a b c]
(prn "got 1 and 3"))
but the following won't really work how you want it to:
(defmethod cares-about-a-and-c [String String]
[a b c]
(prn "Got two things that match (isa? String)"))
(cares-about-a-and-c "foo" nil "bar") ;; doesn't call our last method
You could, however, define something based on class and not isa? via your dispatch function:
(defmulti cares-about-class-of-a-and-c
""
(fn [a b c] (mapv class [a c])))
(defmethod cares-about-class-of-a-and-c [String String]
[a _ c]
(println "Got the strings: " a " and " c))
http://www.lispcast.com/solid-principles-in-clojure
which elaborates on SOLID OO design in Clojure and provides some code examples. For example there's a pretty clear example of the right way to apply SRP to a functional paradigm.
I'd interpret it as you need SOLID to do successful OO (required but not sufficient), but using as much SOLID as reasonably possible under any paradigm also happens to result in good/better software. In that way there can be a lot of confusion about what is OO and what is merely usually found nearby OO but isn't OO specific.