Fork me on GitHub
#off-topic
<
2021-10-19
>
Asko Nōmm02:10:15

Meanwhile I got myself a VPS, and moved my dev env there, for easier scaling per-need. What I do need, is a decent monitor that works with M1 Macbook pro. (preferably at least 4k), and I have no idea where to even start :(

sova-soars-the-sora03:10:46

definitely read macrumors to make sure you're buying at the right time

lilactown05:10:39

i want the promotion display

lilactown05:10:04

gonna wait until i can see one in person. but very interested in the 14"

indy05:10:11

These Apple events are better than most movies

1
lilactown05:10:34

it's tempting to max it out (no pun intended) but I really don't know if I need the 32 core GPU

Stefan06:10:20

And to make it more complicated: it’s not about whether you need it now, but also about whether you need it two years from now 🙂

Yuner Bekir06:10:59

Is it just me or did vs code change the default color palate for their dark theme?

pez07:10:25

If you are using Calva, @yuner.bekir , the latest version brings in semantic tokens from clojure-lsp. But in a rather brute way. An update is cooking that makes it less jarring.

pez08:10:59

The issue (still assuming you are using Calva) should be fixed now, @yuner.bekir. https://clojurians.slack.com/archives/CBE668G4R/p1634631554217300 Follow ups in #calva

🙌 2
Yuner Bekir08:10:20

Thanks. I was using calva ❤️

respatialized16:10:06

https://www.youtube.com/watch?v=MHZDXC4zJ0c This talk is pretty wild. Turns out you can turn almost any function into a pure function and automemoize it if you look for and exploit correlations between the inputs and outputs of CPU registers, and this technique yields benefits even for inputs never seen before.

👌 2
👀 1
Ernesto Garcia21:10:11

Hi. In https://youtu.be/rI8tNMsozo0?t=1776, Rich Hickey advocates for no encapsulation of data. Isn’t there any value in being able to abstract away implementation details? For example, if I have an implementation where Full Name is given, I will be able to internally change its representation from a single string to 2 strings with first name and surname, with no changes in clients.

phronmophobic22:10:52

You can still have a function, get-full-name, that receives a map representing a user and returns the derived value of the full-name. It might be helpful to explain what you mean by "represented internally". I think one of the ideas is that OO programming practices that creates arbitrary interfaces to data is mostly unnecessary and just adds complexity and bloats your program. Having interfaces that abstract is a good thing, but "encapsulation of data" is kind of a non sequitur. Data is just data.

☝️ 2
dgb2322:10:18

@U01GDBB48QN a way of thinking about this is to take the perspective of the function/protocol/multimethod level. Your caller doesn’t just get that data out of thin air, they are calling something that provides it. So if you think “internal” you can instead see it as one step in a sequence of transformations until it reaches that interface. Another thing you can do is to just provide both in some way, the composite and the components. Or maybe it helps to think about this in terms of SQL? Think views and generated (AS) columns. You’re not “hiding” data, you just provide different views on that data.

Ernesto Garcia22:10:06

> You can still have a function, `get-full-name`, The introduction of that function would break clients, which would be referring to a map. > “encapsulation of data” is kind of a non sequitur Encapsulation of data representation is a thing. You have mentioned the get-full-name function above, for instance.

hiredman22:10:10

if you don't to break clients the correct thing is to make additive changes only, if you want a different set of fields add them and merge to the existing set of fields and provide everything

dgb2322:10:11

And I agree with @U7RJTCH6J here. The terms “data hiding” and “information hiding” are not very useful. “Complexity hiding” might be a better one, you provide an abstraction, function or a library, so your caller doesn’t have to think about all of the details. “Providing functionality” might be the most accurate.

phronmophobic22:10:21

To me, get-full-name is transformation of data rather than encapsulation of data.

Ernesto Garcia22:10:04

> You’re not “hiding” data, you just provide different views on that data. That’s what I understand Rich is advocating against. He advocates for providing data as maps.

phronmophobic22:10:49

I'm still not sure what you mean by "represented internally"

dgb2322:10:29

> That’s what I understand Rich is advocating against. He advocates for providing data as maps. Are you familiar with SQL views and generated columns? I thought the example was illustrative. They are transformations on data, they don’t hide anything

dgb2322:10:12

Oh I watched the part you were referring to. I think there is some misunderstanding here. He is not advocating against functions that provide some derived forms of data. He advocates against having a predefined, idiosyncratic, non-generic method interface to every piece/unit/field.

Ernesto Garcia22:10:56

But those interfaces actually protect clients from changes in representation. So they do have a value.

dgb2322:10:20

Ah yes, it is common to have functions in namespaces that are not part of your API.

dgb2322:10:50

“don’t use this stuff here it might change”

phronmophobic22:10:57

I think a key idea is that those interfaces around data really don't protect clients from changes in representation. They just add bloat. The part of your program that will live a long time is the data. The data will be stored on disk, sent to other programs over the network, or otherwise. Once it's out there, you can really change it and the extra layers just make it harder to understand.

Ernesto Garcia22:10:03

In that case, is Rich’ statement wrong? I’d be pretty sure he has some reasons to make that point.

dgb2322:10:54

No he is talking about a different thing

dgb2322:10:04

Which language are you most familiar with?

dgb2322:10:42

I’m trying to think about examples or something to illustrate how they are two different things

Ernesto Garcia22:10:51

> those interfaces around data really don’t protect clients from changes in representation But they do.

Ernesto Garcia22:10:18

> Which language are you most familiar with? I don’t know… Clojure?

phronmophobic22:10:58

It's still the "Referer" header because you can't change it.

phronmophobic22:10:11

For some programs that never communicate with other programs, the outside world, or persist data, then it's possible that the interfaces can protect clients, but most programs persist data, communicate with other programs, and talk to the outside world

hiredman22:10:39

and this goes hand in hand with clojure eschewing the creating of custom types to represent data

Ernesto Garcia22:10:40

HTTP is a protocol, and therefore it is fragile to changes. That’s why protocols take time and extreme care to get right and standardize. We don’t want our code to behave like that.

hiredman22:10:30

it behaves like that regardless

Ernesto Garcia22:10:02

Thanks for the discussion. I need to go, but I’ll follow up.

p-himik22:10:05

I see it this way. Extra layers of anything have both advantages and disadvantages. The main point about data encapsulation is that there are many more disadvantages. The trade-offs are not worth it.

dgb2322:10:01

I think you’re making a very important point here @U01GDBB48QN that is worth discussing. Have a good one.

dgb2322:10:22

I think we do encapsulate. Just inside the function level and not at the data level, both are first class.

phronmophobic22:10:37

One major difference is that encapsulation implies that there is one canonical representation of "User" or whichever domain entity whereas using functions acknowledge that there may be multiple representations of a domain entity for different use cases, even within the same program. There's not necessarily a canonical representation.

didibus06:10:19

> Isn’t there any value in being able to abstract away implementation details? There is, that's what he says in the talk, that encapsulation is for hiding implementation details, and Clojure offers a lot of ways to encapsulate implementation details when needed, such as private vars. What he says is bad is encapsulating information. > For example, if I have an implementation where Full Name is given, I will be able to internally change its representation from a single string to 2 strings with first name and surname, with no changes in clients So think about this, if you were to encapsulate this information behind a method inside a class you'd have:

class Person
  + getName : String
So at first our information was a single name. Now we've somehow managed to know the distinction of first and last, so we want to have firstName and lastName, you'll probably do this:
class Person
  + getName : String
  + getFirstName : String
  + getLastName : String
Now look at this a few time and tell me what's the point of these methods over just a map which would have started as:
{:name "string"}
And then becomed:
{:name "..."
 :first-name "..."
 :last-name "..."}

didibus06:10:51

So he's saying there is no point in wrapping information, information is passive, it is just data, so keep it as data. Here's a bigger walkthrough. First we model a Person as a simple map with :name key.

(defn make-person
  [name]
  {:name name})
Now someone starts to depend on this information:
(defn say-hello
  [person]
  (println "Hello " (:name person)))

(-> (make-person "Johny Cash")
    (say-hello))
Now we somehow change to collect people's first and last name separately, so we refactor, but don't want to break existing use of our information model:
(defn make-person
  [first-name last-name]
  {:name (str first-name " " last-name)
   :first-name first-name
   :last-name last-name}))

(-> (make-person "Johny" "Cash")
    (say-hello)) ; It still works!
If we had encapsulated the information we'd have done either the same thing:
class Person {
  Person(String firstName, String lastName) {
    name = firstName + " " + lastName;
    this.firstName = firstName;
    this.lastName = lastName;
  }
  String getName() {
    return this.name;
  }
}

sayHello(Person person) {
  System.out.println(person.getName());
}
Or we'd have done this:
class Person {
  Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  String getName() {
    return firstName + " " + lastName;
  }
}

sayHello(Person person) {
  System.out.println(person.getName());
}
Only that second refactor is not possible in Clojure, but as we said, information is passive, in that way there's no real benefit to that second way of refactoring over the first. So this is what Rich Hickey is complaining about, he's basically saying its just stupid to do this and provides no real benefit while adding a lot of additional verbosity and being less malleable.

Pagoda 5B09:10:52

I'm not sure how familiar you might be with FP vs. OOP approaches, but essentially FP takes a stance in separating clearly functions from data-structures. The latter are open and available for anyone to peek and reach into. The functions is where the abstractions lay, functions provide "encapsulation" of implementation details. This is at odds with the OOP approach of mixing up data and functions, and the whole idea of hiding the data thru layers of functions/methods indirection. I think this is what stands out as highly impractical and is the point being made in the talk?

☝️ 1
Pagoda 5B09:10:29

Data is just data, might be an easy-to-remember slogan for this

Pagoda 5B09:10:02

Encapsulating data in custom-made Classes/Types definitions leads to a huge proliferation of custom definition which in turn requires custom handling. As opposed to having few data-structures and generic functions that can work on most of them.

Pagoda 5B09:10:45

I'm seeing this a lot right now in my past few projects where handling different protocol formats (json, protobuf messages, ...) using typed representations (as promoted a lot in Scala/Haskell) requires you to constantly rework any code handling those messages, for each tiny change you make on them. Instead using, say, maps, means that you can always add/remove things without creating a new type. And any function can make his own call as to what it needs to have in such map, ignoring what's useless to his own goals.

Pagoda 5B09:10:32

@U01GDBB48QN you might also wanna check https://www.youtube.com/watch?v=YR5WdGrpoug to get a more clear picture

Ernesto Garcia18:10:37

> Only that second refactor is not possible in Clojure, but as we said, information is passive, in that way there’s no real benefit to that second way of refactoring over the first. There is a real benefit: if you change the name of the person, the full-name will still calculate correctly. There is a functional dependency of full-name on first- and last-name that just data is not able to express. The original implementor can also decide to lazily calculate the full-name, optionally cache it, etc.

Ernesto Garcia18:10:22

> FP takes a stance in separating clearly functions from data-structures Not necessarily. In FP you can define functions that operate on a single data structure.

Ernesto Garcia18:10:00

Information does have an implementation, which is the way you represent it in your computer. This can vary as a program evolves, and therefore you can break clients that make use of it. That’s why I’m wondering what’s the idea that Rich is trying to communicate by saying that there is no such valuable thing as encapsulating data.

phronmophobic18:10:27

> The original implementor can also decide to lazily calculate the full-name, optionally cache it, etc. This type of change is only really available if the data only lives within the program. Once the data is stored, transmitted, shared, or otherwise, then it's no longer an option. I'm actually trying to recall a time when I've either wanted a "computed property" and not had it or had a computed property provide some concrete benefit. I can't think of one. I can recall many instances where I wish a program exposed a data driven interface and it was a real pain to work with.

👍 1
phronmophobic18:10:18

I can imagine encapsulation being helpful in a program for something like a game, that doesn't interact much with the outside world, but most software I write is for systems of programs.

didibus21:10:05

> There is a real benefit: if you change the name of the person, the full-name will still calculate correctly. A preface that I didn't bring up is that part of the reason encapsulation isn't needed is because of immutability. So in this case, you would not change the name of an existing instance of a person, once created it is immutable. If your feature allowed a user to update their name for example, you'd need to create a new person, and again at creation of that new person, it would recompute the name. > There is a functional dependency of full-name on first- and last-name that just data is not able to express. That's because we don't agree on what we mean by "data". Data does not have these sort of calculated dependencies, if your domain has this, then you want to model it as a function. Data is simple information. If you capture a person's name, there is no concept of calculated name, my name is just my name, there is no calculation needed to know my name, it is just what it is. What you want to capture is a representation of my name. So information of my name would be things like the values it involves and the understanding of its parts and whole. You're right though, if you get this wrong the first time around in Clojure, you need to refactor. So if say you wanted to represent the sum of money in my wallet, and that's not a real thing, my wallet doesn't contain a sum, it has individual bills and coins, so if you thought ok :wallet-sum should be data, that was a mistake in the model, it should have been a function, so ya if you start with data when a function was needed, you will need to refactor. Here's a quote from Rich Hickey to explain this trade off though: > Fogus: Following that idea—some people are surprised by the fact that Clojure does not engage in data-hiding encapsulation on its types. Why did you decide to forgo data-hiding? > Hickey [creator of Clojure]: Let’s be clear that Clojure strongly emphasizes programming to abstractions. At some point though, someone is going to need to have access to the data. And if you have a notion of “private”, you need corresponding notions of privilege and trust. And that adds a whole ton of complexity and little value, creates rigidity in a system, and often forces things to live in places they shouldn’t. This is in addition to the other losing that occurs when simple information is put into classes. To the extent the data is immutable, there is little harm that can come of providing access, other than that someone could come to depend upon something that might change. Well, okay, people do that all the time in real life, and when things change, they adapt. And if they are rational, they know when they make a decision based upon something that can change that they might in the future need to adapt. So, it’s a risk management decision, one I think programmers should be free to make. > If people don’t have the sensibilities to desire to program to abstractions and to be wary of marrying implementation details, then they are never going to be good programmers.

didibus21:10:37

But again, I'd like to talk in code, nothing explains things better than actual code.

(defn make-wallet
  [bills coins total-dollar-amount]
  {:total-dollar-amount total-dollar-amount
   :bills bills
   :coins coins})

(defn make-bill
  [currency value]
  [:bill currency value])

(defn make-coin
  [currency value]
  [:coin currency value])

(make-wallet
  (concat
    (repeat 3 (make-bill :usd 5))
    (repeat 2 (make-bill :usd 10)))
  (repeat 5 (make-coin :usd 1))
  40)
Now here you want :total-dollar-amount to be in-sync with the number of bills and coins of various value, otherwise your information is wrong and will get corrupted. Lets assume we started where a wallet only had a :total-dollar-amount that the user calculated in their head and entered manually into the system, but later we switched it so the a machine counts the dollars and coins in the wallet automatically and we want to compute the :total-dollar-amount based on those. So like you said, you already have code that does (:total-dollar-amount wallet). So you can choose to refactor that code to use a function (total-dollar-amount wallet), and like Rich Hickey says, that can be acceptable, people adapt, it's not the worse of refactors, and maybe the need for it is rare so the pros of not encapsulating might be worth it. But practically, let's explore the scenario some more. What if you change it too:
(defn make-wallet
  [bills coins]
  {:total-dollar-amount (compute-total bills coins)
   :bills bills
   :coins coins})
The problem is solved again. Code can continue to use (:total-dollar-amount wallet) without issue. Now you say, what if I modify the total? Okay, but how would the code have looked like that did that?
(make-wallet 345) ;; Assuming this was when we only took a :total-dollar-amount

You'd have had:

(update {:total 12} :total-dollar-amount (fnil (partial + 100) 0))
This is in the old way, users would just enter the total. So you might say this code is broken now, because now if you update the total like that, it will be out of sync with bills and coins. Yes, that's true. If someone had used setTotal instead and encapsulated, you could maybe retrofit this, except in this case you can't. Because you can't go from a total to knowing the breakdown of it in terms of bills and coins. So in this user feature change, you still need to go and refactor where things update so they too now provide bills and coins. So its the same amount of refactoring, and you'd change the places that updated the total to be:
(make-wallet
  (concat (:bills wallet) added-bills)
  (concat (:coins wallet) added-coins))
Which you could wrap in a util called add-bills and add-coins if you wanted, etc. So my point is that the theory of encapsulating information sounds awesome, but in practice it seldom really saves you much from having to refactor things, because in practice you can't just magically pretend that something that is computed or remotely queried is "just data". And the downside is added verbosity, indirection that hides the true data from other programers and might confuse them, not able to easily marshal and exchange the info over a wire, not able to reuse all the existing map/vector functions, etc. In fact, from my experience, I can imagine someone actually making up some bills and coins on setTotal and eventually leading to a big mess up to the business with super hard to find bug until someone realizes that to save themselves a refactor someone just thought they'd make up a breakdown of bills and coins to match the new total, thus making it the wallet no longer matched the real wallet, etc.

didibus22:10:41

Now, keep in mind, if bills and coins were mutable, you'd need to hide them, because of race condition and all that. You'd also need to make total into a function, because the constructor could be avoided, and the constructor is where we assert our invariants right now. That's why when you use deftype in Clojure, it supports encapsulation, and in fact, it doesn't even allow to expose a mutable field publicly, it forces you to encapsulate it always. So every time you think why not encapsulate, you can't think about Java or Ruby, those languages have a context that makes encapsulation super useful to them. You have to ask yourself, in the context of Clojure, how useful would encapsulating information be? And in my experience, and to Rich Hickey's opinion, in practice, it's really not all that useful, and just becomes an additional pain point for other things.

didibus22:10:46

Same thing with the name example as well by the way. You start with :name only, then switch to capturing :first and :last. Okay cool, you just compute :name at construction, problem solved. Now you say, what about the code that updated :name ? Well that code is broken, what are you going to do? Use Machine Learning to try and figure out which part of the :name is the :first and the :last? How do you go from a full name into a :first and :last? Obviously if you change to asking the user for :first and :last separately, you also want to change where the user is allowed to update their name so it to lets them update it as :first and :last separately. Otherwise why'd you bother changing it to :first and :last in the first place?

didibus22:10:14

There's one last scenario, which is where you'd tell me: But didibus, what if someone wants to change the :first-name afterwards? Okay good question:

(defn make-name
  [first last]
  {:first first
   :last last
   :name (str first " " last)})
Now you say, it would be a mistake for someone to do:
(assoc name :first "Bob")
Because you updated :first, but now :name is broken. But this isn't a refactoring issue. This is just that you need to learn how to manage invariants on entities and your domain model in Clojure. There are two ways that are often combined: 1. Have functions that deal with the invariants, and modify your entities using them. So like I said, either keep using the same constructor to update and have the invariants in that constructor.
;; Instead of (assoc name :first "Bob") do:
(make-name "Bob" (:last name))
Or have some utils:
(defn update-name
  [name & {:keys [first last]]
  ...)
And you can capture the invariants in those. Yes, this requires the programmers to know that they should use those functions to modify things in an invariant preserving way, but Clojure always trusts the programmer to be good. 1. Use a spec and validate The other option is to have a spec and validate that your changes to the entity are valid to it after you've done them. Spec can capture relations such as that name is the concatenation of first and last with a space between them. Again, this also requires to some extent that the programer remembers to validate, though you could have already put some validation spots in some places that could catch it for them. Often time people actually combine 1 and 2 together.

didibus22:10:18

Sorry for the looooong post. Not easy to summarize all this. I feel like I got myself a blog post here maybe haha 😛

😄 1
AJ Jaro20:10:03

Thanks @U0K064KQV. I am not sure, however, what the benefit of writing specific constructor functions or util functions is if those aren’t standard practice in the codebase already. You are right that programmers need to know to use certain functions to perform these operations, so why not make that a requirement when creating a first-class entity. Then developers always know where to go to find how something is defined. The overhead of using a protocol, spec, or any other tool to me seems all about the same. It’s a tradeoff in one area of the codebase for benefits in another. 🤷

didibus02:10:15

@UGMAVSMUM Absolutely, they are trade offs, but I'd say a code base needs to establish a standard practice for itself. It is pretty fundamental for an app to model its domain one way or another. If you don't have a constructor, or don't have a spec, it would get messy quite fast. If a user was created in many places, each place needs to stay in-sync with how to create a user no? Assuming they are modeling the same kind of user off course. I guess I would say if you have re-implemented/duplicated the logic of creating a user in many places, you should refactor them to all use a common constructor function. Or at the very least, you should create a Spec for user and have each place validate that what they constructed is valid to the spec for it.

didibus02:10:44

> so why not make that a requirement when creating a first-class entity. Then developers always know where to go to find how something is defined. I think that's fundamentally not Clojury 😛 . What you describe amounts to a framework, something that restricts how you can model things, and forces you to use specific ways. That's a criticism of OOP you'll see sometimes from Clojure folks. I would say in Clojure, all these abstraction can be modeled with simple functions and a convention over them. Just like library vs framework, it might make it more confusing at first how to organize things correctly, since there is no frame of reference, but it is much more flexible.

didibus02:10:39

I'm not sure if this is still about information encapsulation though. For example, you can chose to use records, and model modification to the record using a protocol. But the data is still all public, anyone who cares could choose to bypass the protocol in order to access the data in the record, and they can choose to make whatever immutable update they want (as-in through a copy). All you're missing is a guarantee that none of your colleagues or future self will by mistake choose to bypass the protocol, as opposed to having some good reason to do so. These type of "protect yourself from bad programmers" just isn't the Clojure philosophy. That's where that Rich Hickey quote I think is really telling: > if they are rational, they know when they make a decision based upon something that can change that they might in the future need to adapt. So, it’s a risk management decision, one I think programmers should be free to make. If people don’t have the sensibilities to desire to program to abstractions and to be wary of marrying implementation details, then they are never going to be good programmers.

AJ Jaro12:10:43

Ok, that’s all fair for sure, yep. It’s always been a bit confusing to me some comments from Rich and how to establish the proper amount of standards. It seems like his examples are usually vague and too small for enterprise clients. I’m sure there are ways to solve this though too; thanks @U0K064KQV

didibus15:10:18

Ya, I'm not sure what others do, and I think one problem of Clojure right now is a lack of such large application design best practices. Its not easy to find online.

Ernesto Garcia17:10:15

Hi, thanks for the discussion. Thank you @U0K064KQV for the reference to the interview. Very informative. I think Rich Hickey is being intentionally provocative in his presentation by speaking sometimes in seemingly absolute statements. But I think what he means is that most of the time it is better to run the risk of clients relying on data directly than building an encapsulation parafernalia, especially when programming systems. Another thing that is confusing to me is that Rich uses the word “representation” as the way the data is exposed to clients, in opposition to “implementation”. In many contexts, though, “representation” is used as the way you implement your data, ie. the way you map it to your data structures and primitive types. (Therefore the term “representation invariant”). Also, he says that “info has no implementation”. This is strange. Of course, if you choose a Clojure map for your data, that’s your implementation. I think he means that info doesn’t need a particular implementation for it, meaning that it can be implemented by your ordinary generic data structures. Regarding comments on immutability, I guess it reduces the odds that you will break clients, but it doesn’t remove the possibility. If you do an (assoc person :name "whatever") on name, you will be obtaining a broken person.

phronmophobic18:10:52

There's some more context in https://youtu.be/2V1FtfBDsLU?t=2268 and https://www.youtube.com/watch?v=YR5WdGrpoug&amp;list=PLZdCLR02grLpMkEBXT22FTaJYxB92i3V3&amp;index=3 > If you do an `(assoc person :name "whatever")` on name, you will be obtaining a broken person. This assumes that there is only one valid way to represent a person throughout your whole program and for every function. That's not true! As I understand, clojure focuses on modeling information the same way as the it is outside of programming: • sparse • open • incremental/cumulative • composable

💯 1
phronmophobic18:10:44

Programming constructs that force you into modeling your information in a closed/constrained way lead to brittle programs that are inflexible because they don't match how information works outside of your program.

didibus21:10:36

Ya, Rich is definitely provocative, because otherwise it's hard to get anyone to reconsider the way they are used to do things and their fundamental assumptions, or at least, it makes for a less interesting talk :p > If you do an (assoc person :name "whatever") on name, you will be obtaining a broken person Yes, you will be obtaining a possibility broken person, but you will not break any other code or use case. And if you are choosing to do this for yourself, you probably know why and have a reason to do so. Clojure treats you like an adult, but it makes sure that if you mess up, you don't break other things as well. It's a different philosophy, it assumes that you know enough about the change that you are making to decide and understand how best to make that change. But it also understands that you might not know about all of the other use of that data, and therefore it isolates your changes through making the data immutable. For example, if I was writing an app, before I persist a Person to the database, I would validate the Person entity against the Spec that protects the invariants of the Person inside the database. If there are temporally some instances of a Person that don't conform to one of the valid state a Person can be in for the DB, that's not a problem, as long as once it gets to the point of persisting it, it would now be valid. It's almost like if you worked with a builder throughout, and only built it before you commit it. This gives flexibility to the programmer who has a new use case to implement. It also prevents them say facing a restriction of the global Person encapsulated entity which might force them to go and change the invariants of that entity, in turn requiring them to now understand why it has all the checks and guards in place and if they are going to inadvertently break something else if they relax or alter any of it for their use case. It also means it makes it more easy to like others said, create bounded context, where if I'm in a different context of the app, and for what I'm doing to a Person maybe my invariants are different, or even the structure I want to use to represent a Person are different, so I can just modify the Person to my liking, but again I can do so isolated in my context without breaking anything else. And finally, I'm still protecting the invariants of other contexts, such as the DB representation, because I validate things in the DB layer against their spec, so if a programmer broke the Person entity, made changes, and eventually tried to persist and overwrite the DB's representation of it, well it would throw an error, and they'd know that they didn't do something right and are about to break other use cases by updating the mutable shared representation which is in the DB of that Person. > Another thing that is confusing to me is that Rich uses the word “representation” as the way the data is exposed to clients, in opposition to “implementation”. In many contexts, though, “representation” is used as the way you implement your data, ie. the way you map it to your data structures and primitive types Think about EDN for example, EDN is not even executable code, it is only a representation of some information. Extensible Data Notation, it is a notation for representing data. If I write EDN on a piece of paper with a pen, I have not implemented anything, yet I've represented my information. This representation can thus transparently be sent over a network from one system to another, it can be printed onto physical paper, it can be displayed to a user on screen, etc. That's what Rich means I think, that information is not implementation. It is more of an interface, a common understanding between users. Sure, you will need to implement a memory scheme for storing this information, and you might need to implement functions to manipulate that information, but the information itself is not an implementation. So back to Clojure, that same information could be in a PersistentArrayMap, it could be in a PersistentHashMap, it could be in a PersitentVector, it could be in a ArrayList, it could be in a Sequence, it could be in a String, etc. You can do something like: #my/as-json {:a 10 :b 20} And this could read the information into a String as JSON. Then you could do: (assoc #my/as-json {:a 10 :b 20} :c 30) and this assoc could be made to work on my JSON implementation so that it adds to it the information of a new associative key/value pair. So in that sense Clojure thinks of information more generally, where EDN is the lingua franka, and you talk in terms of this generic representation that even has meaning on a piece of paper. And from there, you can choose one or more implementation for it, including how you serialize or persist it on disk/db, etc. The key thing here is that you are manipulating your data representation, when I assoc, or update, or dissoc or get, those functions know nothing of the information contained there-in. This is in contrast to a class that exposes the data through getName(Person p), which obviously is coupled to the information, you've created an implementation of your information and therefore it only works for that specific representation of information that are the exact hidden fields of that class. > Also, he says that “info has no implementation”. This is strange. Of course, if you choose a Clojure map for your data, that’s your implementation. I think he means that info doesn’t need a particular implementation for it, meaning that it can be implemented by your ordinary generic data structures I think here he means that information is not a computation. Information is something you've acquired either from the real world as input, or as output from some computation that was deriving more information from existing information. This might be the key difference with the Unified Access Principle often used in OO, that pretends like all information is derived and computed, or make it impossible for the consumer to know if it is being derived and computed when accessed or not. So information is data, such as what you'd have in a primitive type or some composite type, or some collection type. And deriving data from data is a function. And in Clojure, that's as simple as you make it. Keep the data as data, though you might organize it inside of various structures like a Map, Vector, Record, etc. And keep logic over it as functions.