| Author is on the verge of having a Clojure epiphany. > 1. You should often be using different objects in different contexts. This is because "data" are just "facts" that your application has observed.
Different facts are relevant in different circumstances.
The User class in my application may be very similar to the User class in your application, they may even have identical "login" implementations, but neither captures the "essence" of a "User", because the set of facts one could observe about Users is unbounded, and combinatorially explosive.
This holds for subsets of facts as well.
Maybe our login method only cares about a User's email address and password, but to support all the other stuff in our app, we have to either:
1. Pass along every piece of data and behavior the entire app specifies
2. Create another data object that captures only the facts that login cares about (e.g. a LoginPayload object, or a LoginUser object, Credential object, etc.) Option 1 is a nightmare because refactoring requires taking into consideration ALL usages of the object, regardless of whether or not the changes are relevant to the caller.
Option 2 sucks because your Object hierarchy is combinatorial on the number of distinct _callers_.
That's why it is so hard to refactor large systems programmed in this style. > 3. The classes get huge and painful. The author observed the combinatorial explosion of facts! If you have a rich information landscape that is relevant to your application, you are going to have a bad time if you try modeling it with Data Objects. Full stop. See Rich Hickey's talks, but in particular this section about the shortcomings of data objects compared to plain data structures (maps in this case). https://www.youtube.com/watch?v=aSEQfqNYNAc |
I kinda like that. Suppose we do something like `let mut authn = UserLoginView.build(userDataRepository); let session = authn.login(user, pwd)`. You no longer get to have one monolithic user object—you need a separate UserDataRepository and UserLoginView—but the relationship between those two objects encodes exactly what the login process does and doesn't need to know about users. No action-at-a-distance.
I've never used clojure, but the impression I get of its "many functions operating over the same map" philosophy is that you trade away your ability to make structural guarantees about which functions depend on which fields. It's the opposite of the strong structural guarantees I love in Rust or Haskell.