But it's never "just data". My password is different in many ways than my username. Don't you ever log/print it by accident! So even if structurally the same, we MUST treat it different. Hence any approach that always only looks at things structurally is deeply flawed in the context of safe software development.
> The difference is that happens in the domain layer instead of the type layer
This view greatly reduces the usefulness of the type layer though, as that's the only automated tool that can help the domain layer with handling cases like this.
Can a human encode something different by that than what they intended to encode? Certainly. But it's got the highest cost-benefit of any approach to double-checking your code I've found.
What's those layers you are talking about? In my domain-logic code I use types of course so there is no dedicated "type layer".
a { user_pw: PasswordString}
This is what it means to model the domain using types. It is not a separate layer, it is actually using the type system to model domain entities.
type PasswordString struct {
pw string
}
func(p PasswordString) String(token string) { ... }
I guess the point is that you can model your domain using data as well as types.But you haven't hidden the information, it's still a string. You can put the string in a wrapper struct but in a structural system that's not really any different from putting it a list or map - the data is still exposed, and if someone writes code to e.g. log objects by just enumerating all their fields (which is a very natural thing to do in those systems) then it will naturally print out your password and there's not really any way to make it ever not.
> I guess the point is that you can model your domain using data as well as types.
You want both in your toolbox though. Restricting yourself to only having types that are essentially lists, maps, or a handful of primitives has most of the same downsides as restricting yourself to not using types at all.
I guess my point is that a structural type system can still allow for encapsulation.
Those sound like decidedly non-structural features. And couldn't you undermine them by passing it to a function that expects a different `struct { pw string }` and logs its contents?
And yeah, structurally typed languages often have nominal features. They come in useful in a lot of scenarios! Unless you're talking about something like Clojure which is not statically typed.
But that domain layer should make use of the type system! That's where the type system is most useful!
I think type-theoretic safety is a completely different thing to the use of types (and names) in software-as-domain-modeling (for example, but not necessarily OO modelling). At different times, for different people, one perspective is more important than the other. It is important not to confuse the perspectives, and to value both of them, but also to recognise their strengths and weaknesses.
One theme that sometimes emerges is that the type theory people don't care about names at all. Not even field names. Taken to the extreme Customer( name: str; age: int) is just a Tuple[str, int]. The words "Customer", "name", "age" have no place in the code base.
My take is that when you are dealing with computer-scientific abstract things, e.g. a List of T, then there is no need to reference the domain entities; placeholder names like T, x, xs make sense. On the other hand, if you're writing an application that models domain semantics (eg. business rules), writing software amounts to modelling the domain, it should be easy to correlate software entities with the real-world entities that they model. To do this we use descriptive words, including names, domain events, activities and so on. e.g. List[Customer] not List[Tuple[str, int]]. Then again, you could replace all of the type names with A, B, C, ... and all the variable names with w, x, y, .... The example would end up as X[Y[Z,W]], the software would work exactly the same, and you might get some insights into the structure of the system. However, if you're in the business of building a user management system in general this is not going to fly for very long with your workmates or your client. You will have trouble onboarding new developers.
You are right, I should have gone further with the example and used Customer{ age: Age, name: PersonName}.
I'm not saying the nominal approach to types is wrong or bad, I just find my way of thinking is better suited for structural systems. I'm thinking less about the semantics around product_id vs user_id and more about what transforms are relevant - the semantics show up in the domain layer.
Take a vec3 for example, in a structural system you could apply a function designed for a vec2 on it, which has practical applications.