29 comments

[ 3.3 ms ] story [ 69.8 ms ] thread
What the article calls "loci" satisfy the axioms of an affine space, with the exception (for example in the case of pointer arithmetic) that the base set might not be a field but only a ring, and therefore it would be a "pseudo-affine" space based on a module rather than a vector space.

Programming by specifying algebraic properties is very easy and natural, but in most languages it's a pain to encode in the type system.

His loci do have an origin though. An affine space doesn't have an origin. So basically, his loci is (canonically isomorphic to) the module.
(comment deleted)
This is all pretty interesting. I definitely like how methodical this all is. I'd be willing to try some of this practically to see where I chafe with it!
A good breakdown, but money resists all of these categories when mixing currencies. Here's a good analysis of various money types:

https://deque.blog/2017/08/17/a-study-of-4-money-class-desig...

… there's a number of (rather trivial?) fixes to some of the problems they hit.

First, you can have a type-checker check the currency; that doesn't have to be a runtime error:

  Money<Usd> m = …;
There will, of course, be instances where you must handle currency dynamically.

"MoneyExpr" is a bit more complicated; I find most people usually implement "MoneyBag" intuitively first, so it's odd they left that for third? So, the article gets there, and we "fix" the problems, but it feels roundabout.

The criticism of MoneyBag is bizarre:

> Compared to the Martin Fowler’s design, it remains heavier. It consumes more memory and more CPU

This is apples to oranges? Fowler's design is a Money type; MoneyBag is a different thing; of course it consumes more memory. A Vec<int> consumes more memory than an int, too?

Perhaps the article author isn't comfortable with that Money merely isn't closed over addition. But if you accept that Money isn't closed over addition, then naturally there must be another type in the type system.

I'm not sure what the confusion is, the question being explored is, "how do you define a Money type that features the operations we typically expect to perform and with the results having the expected properties?". He reviews various well-known published designs and their drawbacks, and outlines a design that does not have those drawbacks.

> I find most people usually implement "MoneyBag" intuitively first, so it's odd they left that for third?

I have not found that at all.

> First, you can have a type-checker check the currency; that doesn't have to be a runtime error: > Money<Usd> m = …; > There will, of course, be instances where you must handle currency dynamically.

With dependent types, checking for currency validity may be handled at compile-time. (This is touched upon in the post linked in the comment you replied to.)

For the case of conversion between currencies of monetary amounts:

https://github.com/anderslundstedt/type-experiments#type-saf...

If you are interested in physical domain types specifically, there are uom and simple-si-units, which can be used separately or in combination.
Reminds me a lot of David Spivak’s “ologs”, down to the example of how dollars are a vector space
Although I disagree with some of the details, I think this type of approach to domain modeling is some of the most valuable work software engineers can do; in fact it's the type of analysis and rigor that justifies the word "engineer" in the title of "software engineer". But I do have two important points to add:

1. Don't restrict yourself to "common" types when you're thinking this way -- all of domain modeling can be approached this way. Working on a Pizza Ordering app? Think about the properties of various operations in your domain and how they are related to mathematical structures. Are they commutative, well-orderable, equatable, multiplicative, scalar, groups/rings/fields, monoids? Are there changes you can make that would allow your operations to have desirable properties? This stuff genuinely does pay off down the road when you want to expand your ideas or re-use your code on something else.

But be careful: not all useful mathematical structures have names or show up in textbooks! The goal is not to map your domain to things that mathematicians have named, it's just to understand it at this level (and get your code to understand it at this level too). If you find yourself arguing over whether it's actually a vector space, a tensor space, or an affine space, then stop worrying about external definitions and re-focus on the properties of your domain. Arguments about how your domain works are good; arguments about exactly what other mathematicians have written are bad.

2. This post is focused on getting the compiler to understand these concepts, but there's still a lot of value in explicitly modeling them in code even if it's "runtime checked" instead of compile-time checked. Many of these techniques end up requiring higher-kinded types to really get the compiler on board, which unfortunately most languages today don't support. But that doesn't mean you should change the abstraction, it just means you get a lower (or later) level of checking.

Years ago, I had concluded for myself that money could be naturally treated as a vector space. It's nice to see that somebody's had the same idea.
Really great and interesting blog post! The different categories of domain types identified all seem useful. It's clear how practical these ideas can be when implementing or thinking about domain-specific types. I'll definitely be keeping these terms in mind for future programming.

> Identifiers have no structure, i.e., we don’t care about their internal representation. The only fundamental requirement is the ability to compare values of those types for equality. This lack of structure suggests an appropriate mathematical model for such types: a set, a collection of distinct objects.

I don't understand what this is trying to say and this "mathematical model" of sets doesn't seem to be applied further on. In math, objects are usually always assumed to have a notion of equality, so I don't see how that alone implies a set. A mathematical abstraction that matches the `Eq` trait would be a setoid.

The `LocusLike` trait is a lot like a metric space with a total order. I wonder if it can be implemented by things that are not basically numbers under the hood (not that it needs to, just curious)?

> Unlike design patterns, the universal domain types are based on mathematical abstractions and can be specified precisely.

I feel like this is over-selling it a bit. I think the ideas are great but, at least as presented here, I don't think they are rigorous enough to deserve the label "mathematical abstractions".

> I don't understand what this is trying to say and this "mathematical model" of sets doesn't seem to be applied further on.

I think what is meant by it is that we have a set without additional structure. Other structures like vector spaces, groups, fields, etc. are (at least in mainstream maths) also sets, but they're equipped with structure - operations and axioms that govern how these operations work. But if you have just a set, you can't assume anything about the internal structure of what's in that set.

Thanks. Makes sense, I think you're right.
The basic problem addressed here is a code smell called "primitive obsession" [0]. Strange name, but worth knowing.

[0]: https://medium.com/the-sixt-india-blog/primitive-obsession-c...

It's a really awful name. Is primitive obsession a good code smell or a bad code smell? And if code smell is a bad thing, and if primitive obsession is a good thing, wouldn't the code smell be lack of primitive obsession?

I don't mean to be pedantic. But it's quite exhausting trying to parse the meaning out of "primitive obsession is a code smell". It took me a while to figure out whether the author considered "primitive obsession" to be a good thing or a bad thing, not least because the they provided an example but not a definition.

The term "code smell" only ever has a negative connotation. The title of the article is also "Primitive Obsession — A Code Smell that Hurts People the Most" -- doesn't seem too difficult to parse...
But the solution being advanced is to prevent inheritance of primitive operators. Implying that Primitive Obsession is a good thing. Unless the author means "primitive type obsession". I imagine they did.
(comment deleted)
This reminds me a lot of value objects from Domain Driven Design
If you appreciate the patterns from DDD and the idea of domain modeling with type systems, I highly recommend checking out "Domain Modeling Made Functional" by Scott Wlaschin.
I recently watched a talk he gave with the same title. It was excellent. Honestly having one of those planning/discovery meetings before any code gets written can prevent so many nasty problems.
Very abstract-algebra-y (loci being probably some generalization of a metric space).

...Why don't we just import he rest of algebra here :)