The comments by Jack and tp1 (which should actually been answers) already explain how this is implemented in functional languages.
My answer adds a point about non-functional languages, especially the one quite popular in development industry: C#.
In languages such as C# or Java, it is indeed a current practice to create types when you need to constraint your values a little bit. If the only thing you need is a positive integer, you'll end up with PositiveInt
class, but in general, those wrappers will reflect a bit more the business logic (ProductPrice
, RebatePercentage
, etc.), again with validation inside (a product price should be superior to zero, a rebate percentage don't accept a value superior to 100, etc.)
The benefits and the drawbacks and the risk of going too far with such types was recently discussed here.
Once you have your wrapper, you may start adding logic to it, and especially the validation. Basically, since the wrapper hides the value it wraps, the validation logic will be located in the constructor, and can be as basic as:
if (value > 100)
{
throw new ArgumentOutOfRangeException("value", "The allowed range is (0..100].");
}
Another possibility provided by C# is to use code contracts, which provides several benefits:
The static checking catches misusing of those wrappers before you hit the error during the runtime,
Code contracts being enforced during runtime as well, you are sure the contract will not be misused even if static checking was disabled (or its warnings were dismissed by the developers),
With invariants checking the wrapped value, there is no way to assign an invalid value (with the limitation that the checks are done when a method starts or ends, and not at every step during the execution of the method),
Visual Studio integration makes the code self-documenting, by providing hints about the contracts to the callers.
Is it successfully taken in practice? Well, there are thousands of methods within the .NET Framework itself which contain code contracts. For business code, it mostly comes to how critical is the code. If the consequences of a failure are expensive, it could be very attractive to use code contracts. On the other hand, code contracts have a substantial cost in terms of developer's time (ensuring all contracts work well on a all but tiny projects requires a lot of time), and may not be a good idea for the projects which don't necessarily need this level of reliability.
This also answers your other question:
I'm wondering: just how far is it practical to take this?
This is a strictness vs. flexibility question.
If you need to develop very fast, and accept the risk of having runtime errors, you'll pick a weakly-typed language. The benefit of writing less code, for example print(123)
outweighs the risk of problems which may be more or less difficult to debug, for example 123 + "4"
(would it result in 127
or "1234"
?)
In this case, types either won't exist or will be managed implicitly by the language/framework. While you could still do range validation (for instance to sanitize user input), it would look weird to do such validation outside the interfaces with the outside world.
If you are working on life-critical software, you'll use formal proof which will take a huge amount of time, but will make sure there are no errors in the code.
In this case, chances are types will have strict validation: range and precision for numeric values, length and allowed characters for strings, etc.
In between, you'll pick the approach which corresponds the most to your needs. For most software, code contracts would be an overkill. But for most software, having basic checking within a type wrapper (such as Percentage
) may be a good idea. Without going into extremes (as discussed in the link already provided above) of creating classes for everything, it could be a good compromise to have a few generic types such as Range<T>
or LengthLimitedString
which are not that difficult to implement in languages such as C# or Java.