> In Clojure parlance, "simplicity" refers to interconnectedness
Clojure tends to redefine words describing concepts with a new or restricted meaning. Typically a general concept like 'simplicity' would have more than one dimension.
> take as as argument an object that has the "id" field from the "Student" class, and the "email" field from the "User" class. Or would you have to explicitly pre-define mixins like HasStudentId and HasUserEmail?
I would not do this at all. Having slots or not are low-level artefacts and writing methods which are fine-grained to slots of an objects don't expose the domain-level.
I would use methods for domain level classes which have the necessary slots either local or inherited.
> Clojure tends to redefine words describing concepts with a new or restricted meaning. Typically a general concept like 'simplicity' would have more than one dimension.
Sure, but that happens all the time in programming. When we talk about "objects" we don't mean "a material thing that can be seen and touched".
> I would not do this at all. Having slots or not are low-level artefacts and writing methods which are fine-grained to slots of an objects don't expose the domain-level.
I guess because it's not at all idiomatic, even in an object system as rich as CLOS.
But in Clojure it is idiomatic, and to my mind it leads to a more precise definition of what a function wants. We don't say, "this is a method that operates on a Student object"; we say, "this is a function that takes data that includes a student's email and enrolment date".
Again, I hasten to add that my experience with CLOS is virtually non-existent, but compared to the OOP languages I am familiar with, Clojure has a far richer way of describing the structure of data.
"this is a function that takes data that includes a student's email and enrolment date"
You can do this using a row type, e.g. OCaml:
# let foo student =
Printf.printf "Email: %s\nEnrolment: %d" student#email student#enrolment
val foo : < email : string; enrolment : int; .. > -> unit = <fun>
The second line is the output from the OCaml REPL. Note how it automatically infers that the input is an object which includes an email and an enrolment field, with the correct types.
I understand that, but as I said I don't like that at all. It binds methods to low-level adhoc features and not to class based domain ontologies. There are other pattern directed invocation systems, which also support that, but I usually prefer the more systematic approach of CLOS. In the tradition of symbolic programming I want to have symbolic classes as my anchors for functionality, not adhoc patterns.
You can still create class-like ontologies with Clojure specs, but usually you don't need to.
Clojure's specs, adhoc or otherwise, certainly might not be your cup of tea. Clojure really pushes the idea of separation and isolation, and in my view you need to buy into that philosophy to be at all comfortable with the language.
Where I disagree with you is your assertion that maps in Clojure are always less structured than objects. I'd claim that (at least when properly spec'ed out) they provide far more structure than objects.
Yes, but as I said, using data structures over objects doesn't mean the data is unstructured. You can use lists, maps and vectors and still have a well defined and validated structure.
Clojure tends to redefine words describing concepts with a new or restricted meaning. Typically a general concept like 'simplicity' would have more than one dimension.
> take as as argument an object that has the "id" field from the "Student" class, and the "email" field from the "User" class. Or would you have to explicitly pre-define mixins like HasStudentId and HasUserEmail?
I would not do this at all. Having slots or not are low-level artefacts and writing methods which are fine-grained to slots of an objects don't expose the domain-level.
I would use methods for domain level classes which have the necessary slots either local or inherited.