Live data from Hacker News

Applying “make invalid states unrepresentable”

kevinmahoney.co.uk

111–120 of 193 posts

Re: Applying “make invalid states unrepresentable”

#111
post #89
post #37

Not certain I get the needless attack on OOP in this text. The error could just as well have happened in any alternative to OO. What is needed is the realization that there is that there is a schedule that needs to have full control over the times to not create coordination issues. That is a realization that is utterly independent of the OOP-ness of the eventual solution.

The problem with a narrow OO mindset is that it encourages encapsulation and atomic objects with hidden state. Simply gluing those pieces together can create suboptimal representations - a more holistic thought process is better.

Not necessarily. Some people can't see the forest for the trees no matter what kind of programming language they're using.

What's needed here is to sit down and think REALLY hard about how the system works and what some of the terms are that are used to describe things. And also to have an intution for when someone is using imprecise language. GP cited use of a "Schedule" object as a way to enforce the invariant. That might represent a block of intervals by a sequence of times.

The less precise you are about what's needed and how the system should work, the more of a soupy mess you're going to build.

People gravitate toward OO because OO makes it feel like you've got a lot of conceptual clarity. You can get back to that familiar "subject/object" dual we love in English. But the problem is actually that, although people can pick subjects and objects readily from a sentence, they might not have much luck picking the important subjects and objects from a sentence. And that's the problem we really need to solve.

Re: Applying “make invalid states unrepresentable”

#112
post #55

Earlier quoted context omitted.

It's not that much more complex: if you do SELECT event FROM events WHERE startDate then all but one of the results (the one with the greatest `startDate`) will also have an implicit end-date prior to 2021.

Until business tells you that endDate is not necessarily greater than startDate. <- real world experience

Sounds interesting can you explain more about it?

Re: Applying “make invalid states unrepresentable”

#113
post #27

While this is great if you know exactly what you want to achieve, it does “lock you in” those constraints on a more fundamental level. More times than I can count I’ve seen business requirements change to require those “unrepresentable” states, and since you’ve now designed you whole data model around it you need to add awful hacks to make it work. The timeline example is actually very telling. A lot of times you’d a…

I agree that this is an issue, but I think the answer is simply using a very flexible basic data representation (which admits invalid state) and then using predicates to refine it. e.g. starting with a list of (start,end) intervals and then adding predicates for valid intervals (start If any of the requirements change, it's easy to either add more predicates or relax/even outright remove them.

If you play your cards right, you can even get your type system to alert you to every place in your code that needs to change when you change the constraints.

I don't deny there's a certain art to that, and I can't explain it all myself. But I don't even necessarily mean amazing clever type tricks like you might see in Haskell or something, I mean that something as simple as "I've changed the definition of what a 'Customer' is in some fundamental manner, so I'm going to rename that class to 'CustomerNew', use the compiler to point me at every single place that breaks, audit it, change the local name to CustomerNew, and then, once everything is fixed, use my IDE's rename feature to rename CustomerNew back to Customer before my final commit". Many times you can get by just by renaming a field or something to similar effect, but in the worst case you may need to audit everything.

It's one of the more tedious bits of the job sometimes, but net-net this can still be a timesaver, if you account for the full cost of the trickle of bugs this sort of thing can prevent.

Re: Applying “make invalid states unrepresentable”

#114
post #86
post #3

This is a good introduction on a conceptual level. I think a large contributor to the problem is story-oriented development, where all that matters in the sprint is "getting it done" and not looking at the broader context. To make unrepresentable states practical, Scott Wlaschin has an excellent write-up here (0). His book (plugged in that article) is also excellent. [0] https://fsharpforfunandprofit.com/posts/design…

Sum types are one of the main things I miss when working in Python. Is anyone aware of any good ways of adding sum types to Python?

I'm not sure you would consider this a good way, but one can implement (unchecked) sum and product types in any language with lexical closures via Scott encoding[1]:

    # data Pair a b = Pair a b
    def pair(x, y):
      return lambda f: f(x, y)

    # data Either a b = Left a | Right b
    def left(x):
      return lambda l, r: l(x)
    def right(y):
      return lambda l, r: r(y)

    v0 = pair(2, 3)
    v1 = left(7)
    v2 = right(9)

    # case v0 of { Pair x y -> x + y }
    print(v0(lambda x, y: x + y))

    # case v1 of { Left x -> x + 1; Right y -> y * 2 }
    print(v1(lambda x: x + 1, lambda y: y * 2))
[1] https://en.wikipedia.org/wiki/Mogensen%E2%80%93Scott_encodin...

Re: Applying “make invalid states unrepresentable”

#116
post #97
post #90

This line raised a huge red flag for me: "If the customer doesn’t have a fixed contract, it is assumed they are on a default contract" No. Don't assume, specify. Explicitly. If this is part of your specification, have a DefaultContract entity of some kind somewhere. And don't call this table just "Contracts", make it clear that those exist in addition to or overlay a default contract. It might sound like overkill, bu…

> If this is part of your specification, have a DefaultContract entity of some kind somewhere. Yes, but not in the database table for contracts. That's how I read this part of the post. I would expect the assumption that if the customer doesn't have a fixed contract, they are on a default contract to be encoded in business logic somewhere in an application that uses this database.

> I would expect the assumption that if the customer doesn't have a fixed contract, they are on a default contract to be encoded in business logic somewhere in an application that uses this database.

That's exactly the kind of harmful assumption I'm talking about. Harmful in that when people act on that assumption and actually implement it that way.

How a default contract might be represented may vary, and of course it is in no way required or even sensible to be stored as a row in the contracts table.

But to think that it is such a fundamentally different kind of data that it should be represented apart from the rest, in a different system, in a different layer, in a completely different form of representation - this is where madness lies.

Re: Applying “make invalid states unrepresentable”

#117
post #65

So Google's protocol buffers have this feature called "required" fields, which enforce schema in the type system. You should never use it. Never. It's one of those things that sound good until you're a few years into the project. Similar to how you should never be using meaningful IDs as primary keys for objects, always use meaningless fingerprint-like integers. Or how all integers should be signed unless you're dead…

Can you elaborate more on the "required" fields point? We've been using a similar feature for several years now in APIs at my work and haven't run into any issues, though we do only use it very sparingly for fields that logically can never be missing. At some point a client has to make the call for what they consider essential, so pushing it in the schema makes this less ambiguous from what I've seen. Maybe it's fine for our use-case (mostly static APIs), whereas what you're saying is good advice in general.

Re: Applying “make invalid states unrepresentable”

#118
post #65

So Google's protocol buffers have this feature called "required" fields, which enforce schema in the type system. You should never use it. Never. It's one of those things that sound good until you're a few years into the project. Similar to how you should never be using meaningful IDs as primary keys for objects, always use meaningless fingerprint-like integers. Or how all integers should be signed unless you're dead…

> And how many integers should actually be strings, unless you're dead sure this is a number. I phrase this as: If it doesn't make sense to do math on it, it's not a number. What does adding one to a customer account number mean? Absolutely nothing -- you get a completely different account number. So it's not a number, but a numeric string.

It's not a string either though: in the same way integer addition (almost always) does not make sense, string concatenation (almost always) does not make sense either. The proper type would allow for equality check and explicit string (de)serialization only.

Re: Applying “make invalid states unrepresentable”

#119
post #26

In general I agree that it's nice to make invalid states unrepresentable, but I'm not sure if I agree that this counts as a fundamental "invalid state". There is nothing about contracts which require that you can only have one active at the same time, or that that current one must be open ended. From a practical point of view it might be advantageous if you maintain only a single contract with a customer at all times…

Hard agree. The type system is no place for business logic. If you want to get fancy, maybe some database constraints you can change later. Business logic is constraints over the representation... not the representation itself.

Also in generally the principle of "make invalid states unrepresentable” ends with the realization that only Idris can properly do it, which is not pragmatically useful.

Re: Applying “make invalid states unrepresentable”

#120
post #52
post #43

Earlier quoted context omitted.

Then the code will be changed. Making up business requirements is the number one reason for instant legacy code. Code is not set in concrete, you can add that flexibility later when it is needed, but making everything overly generic to make it easier to "implement new requirements" only leads to code that is hard to change in my experience. Also don't forget that this is only an example.

The YAGNI (you aint gonna need it) principle overrides the DRY principle imo.

This is partly why I've found it helpful to wait until there are at least 3 identical (not nearly identical) implementations of something before trying to make a more generic/abstracted version of it.
Post reply on HN