1

When designing types and their APIs, I try to adhere to these simple principles (which vaguely feel like one general principle to me) as much as feasible:

  • There is a one-to-one correspondence between possible states/values of the type (combinations of possible property values, etc.) and their domain interpretations. So there are no invalid states, and also no redundant states that mean the exact same thing as other states.

(A common violation is when there's a type with some boolean property HasFoo, and two optional properties Foo and Bar where Foo is only meaningful when HasFoo is true, and Bar only when HasFoo is false. If this isn't enforced by the type system, Foo and Bar can both be set at once — redundant states; or neither or the wrong one can be set — invalid states.)

  • An instance of the type is valid as soon as it is constructed; all methods and properties are valid to call or access at any time, and can be called in any order, with consistent semantics.

(e.g., .NET's IEnumerator<T> expresses iteration via MoveNext() and Current members, where you need to call MoveNext() and have it return true before Current is valid to access; Rust's Iterator trait has a better API, with a single next() method which returns an Option<T>, so calling it is always valid and it just returns Nothing once the iteration is exhausted.)

I think of this principle as “state hygiene,” and it seems pretty important and basic to me, but I'm not sure I've ever quite seen it spelled out anywhere in all the reading about programming I've done, though it no doubt overlaps with other common concepts (e.g., database normalization). Also, I feel like much language and API design really hasn't been too conscious of this until recently. For example, a language feature that often really helps with state hygiene — including in both of the examples I gave here ― is sum types/discriminated unions, and their special case of optional nullability or an Option type. But up till 2014 or so those were treated as exotic features by the mainstream, and they're just now starting to make it in to languages like C#.

So my question is: is there a better name for this principle? Or how would you characterize it?

8
  • 1
    The only name that comes to mind is "strong typing". The issue is that lots of type systems are not expressive enough to encode all possible situations like that.
    – Euphoric
    Commented Apr 21 at 6:25
  • 1
    This is typically called an Always valid domain model.
    – Rik D
    Commented Apr 21 at 7:08
  • 5
    There's a pattern "make invalid states unrepresentable" that's popular in the ML family of languages, from which Rust is strongly inspired. The term was coined by Yaron Minsky and popularized by Scott Wlaschin. But some aspects of this can also be done in languages without sum types. The "typestate pattern" is related in that it makes invalid operations unrepresentable.
    – amon
    Commented Apr 21 at 7:38
  • 1
    Duplicate of Why are invariants important in Computer Science
    – Basilevs
    Commented Apr 21 at 10:25
  • 1
    @amon: You should post that as an answer since (AFAIK) this it the most common name for what the OP describes.
    – JacquesB
    Commented Apr 21 at 14:33

1 Answer 1

5

This is a type invariant - a condition that is true for an instance of a type for an external observer for the duration of a valid program.

In most imperative languages invariants are enforced dynamically and with minimal help from type system (besides encapsulation).

"State minimization" is usually talked about in context of DFAs, but is applicable to arbitrary types in imperative languages. It describes a procedure to eliminate unnecessary states while maintaining the overall guarantees and achieving the same tasks. In context of OOP it implies creation of as many invariants as sensible. The rationale is that elimination of states and related side effects greatly reduces analysis scope during debugging.

Not the answer you're looking for? Browse other questions tagged or ask your own question.