functiondesign-patternstypedguard-clause

Is there a name for the design concept in which a function's parameter type enforces the constraints to eschew guard clauses?


Suppose I have a (pseudo-code) function divideTwenty.

fn integer divideTwenty(int divisor) {
   return 20/divisor;
}

This function is simple, but error prone. Specifically, what if divisor is 0? What's the best way to deal with this? There are countless answers. But for now, well, we could add a guarding clause.

fn integer divideTwenty(int divisor) {
    if(divisor == 0) { 
        return -1; 
    }
    return 20/divisor;
}

This is still not great (can divisor be negative?), but let's just focus on the clause. There is an alternative approach, that accomplishes the same spirit. What if instead of passing int divisor we passed NonZeroInteger divisor?

That is,

class NonZeroInteger {
    int value;
    fn void init(int allegedNonZeroInteger) {
        if(allegedNonZeroInteger == 0) { throw InvalidArgumentException("Number is zero"); }
        self.value = allegedNonZeroInteger;
    }
}

fn integer divideTwenty(NonZeroInteger divisor) {
    return 20/divisor.value;
}

My question is very simple. Is there a specific name for this kind of design pattern? That is, where we create classes (or types) explicitly for the purposes of establishing constraints on the data being provided to functions. Is it just illustrative of the general use of typed programming, or OOP, encapsulation, or something else?

Thanks.

This isn't a troubleshooting problem. I'm just asking a question about code design concepts.


Solution

  • You are describing a design-concept that is summarized as "parse, don't validate". It is not exclusive to OOP and it works particularly well on immutable objects.

    Your NonZeroInteger-class parses the int to be a NonZeroInteger. Parsing, in this case, means to take an input with few constraints and return an output with the same meaning but more constraints that you can rely on. Parsing might fail if the unstructured input does not fulfill the constraints. This is not what you usually think of when you read something like "Json-parser", but it is consistent with this definition.

    Operations can now rely on the structure of NonZeroInteger. This is good, because the signature of divideTwenty(NonZeroInteger) now describes to the programmer and the compiler/type-checker what kind of inputs it can take, while the old divideTwenty(int) method actually only works on a subset of its statically allowed inputs.

    Other formulations of this design-concept are "make invalid state unrepresentable" and the concept of preferring "complete functions" over "partial functions". You may look up "phantom types", they are a sophisticated implementation of this concept.

    You can read an excellent and detailed article about it here, the examples are in haskell but you don't have to be a haskell-programmer to read it: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/