Didier, I think your synopsis is right on the mark. I shifted from the canonical class-based data model for entities (primarily for discrete event simulation, with applications to games) toward a functional ECS approach a while back - even before I found clojure in 2010. The biggest utilities I find are a) flexibility in data definition(s) - add/remove components as needed, b) unified storage medium for entity information (like an in-memory database, with the ability to easily define aggregate queries and other relational stuff), and c) the ability to operate on domain-specific slices of entities (via selected components). There's a similarity between shifting to an ECS model and using clojure maps for generic data storage; lots of flexbility and unintentional re-use.
My (work in progress, but useful) implementation of ECS (from a melange of libraries called
spork):
spork.entitysytem.store.This is useful, albeit research-quality software that I'm still adapting to my needs and documenting. If nothing else, there may be pedagogical value for other folks.
I took the approach of pushing the entitystore behind some fundamental protocols, and evolved functionality as I ran into problems. Like relational databases, you can take a column or row-based approach to implementing an entitystore (where component data is defined). Depending on your needs and use-cases, either may be appropriate. I stuck with a column store, with IEntityStore protocols implemented on a record that stores information about entity component membership (has-a?) and the actual component domain data. So, we end up with a structure like {:entities {:id1 #{:name :domain2 ...}} :domains {:name {:id1 "SomeName"}}} as our "store". From there, we can define operations on how to add, drop, update, modify entries in the store. Convenience functions, like implementing sql-like queries are easy to implement too. You can probably stick datalog or other unification-based queries on the store since the structure is pretty easy to pick apart. I opted for clojurey names for operating on entity components, ala "assoce, updatee, mergee, assoc-ine, ..." which provide a familiar layer (for me) over the like-named clojure functions.
When you stick with the intended use-case [designing "systems", i.e. functions that operate on one or more domain of component data, to compute new entity stores and the like] the scheme works really nicely. Specifically, systems that are tied to one component, or entity stores with a relatively small number of components that are cheap to join. About 1/2 way into fleshing out the design and putting it through its paces, I realized that i) I often don't care about all the components of an entity.....but I may not know that information ahead of time; it'd be nice to not have to compute joins for every entity every time on certain systems ii) updating entities by-component can be very inefficient, so batching updates would be nice....
This cried out for either a row-based implementation, or a pseudo-row based representation of an entity that avoided paying costs for computing joins across components, while allowing for the column-based properties of the backing store. I ended up implementing a lazy entity record, which provides a map-like reference to the entity's components but performs joins lazily and on-demand, and kept track of changed or dirty components smartly. This alleviated most of the performance concerns I ran into, and enabled me to define and additional set of operations on entities as if they were row-based, i.e. clojure maps.
Regarding inheritance and some of the class-based things you brought up, I baked up a little DSL for defining entity constructors (really just functions making maps) that's deriving partially from the CLOS way of defining structs (with inherited fields and the like). I thought it was pretty slick when I first cooked it up (after reading Land of Lisp), but I don't really use it all that much. In practice, you can just create a map and shove it into the entitystore, which implies dissecting the key-vals of the map and associng like component entries for the entity (assuming a :name key exists). Or, you can create complex "class-like" hierarchies and use that to alleviate some of the burden. Or you can make your own composition of functions - operating on data - to achieve the same result.
Currently, I have the entitystore paired with a behavior tree system that I use for entity behaviors / "ai". I'm still feeling it out, but it works and provides a way to compose sophisticated behaviors that gets away from the FSM spaghetti I ran into originally. In this case, the entitystore provides another advantage, since you can use it as a blackboard for communicating information between multiple concurrent entities. Rather than "firing" events and side-effecting, you can use the presence/absence of data via components to communicate. You can still implement messaging via component data as well. Communication (or simulating communication between entities) was one of the harder problems that seemed to be left as an "exercise for the reader" in much of the ECS literature/presentations...
For me, the combination of an entitystore and behavior trees has ended up fairly robust in practice. It's even surprisingly performant for some real-time 60fps visualizations I've done (tied to a simulation), where I thought I'd have framerate problems. I'm still optimizing (for faster simulation runs), looking into varying implementations for the store - mutable, async, row-store.