In this post I want to discuss several advantages of defining lightweight non-negative numeric types in Scala, whose primary benefit is that they allow improved type signatures for Scala functions and methods. I’ll first describe the simple class definition, and then demonstrate how it can be used in function signatures and the benefits of doing so.
If the following ideas interest you at all, I highly recommend looking at the ‘refined’ project authored by Frank S. Thomas, which generalizes on the ideas below and supports additional static checking functionalities via macros.
A Non-Negative Integer Type
As a working example, I’ll discuss a non-negative integer type
NonNegInt. My proposed definition is sufficiently lightweight to view as a single code block:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
The notable properties and features of
NonNegIntis a value class around an
Int, and so invokes no actual object construction or allocation
- Its constructor is private, and so is safe from directly constructing around a negative integer
- It supplies factory method
NonNegInt(v)to construct a non negative integer value
- It supplies implicit conversion from
- Both factory method and implicit conversion check for negative values. There is no way to construct a
NonNegIntthat contains a negative integer value.
- It also supplies implicit conversion from
Int. Moving back and forth between
NonNegIntis effectively transparent.
The above properties work to make
NonNegInt very lightweight with respect to size and runtime properties, and semantically safe in the sense that it is impossible to construct one with a negative value inside it.
I primarily envision
NonNegInt as an easy and informative way to declare function parameters that are only well defined for non-negative values, without the need to write any explicit checking code, and yet allowing the programmer to call the function with normal
Int values, due to the implicit conversions:
1 2 3 4 5 6 7 8
This short example demonstrates some appealing properties of
NonNegInt. Firstly, the constraint that index
j >= 0 is enforced via the type definition, and so the programmer does not have to write the usual
require(j >= 0, ...) check (or worry about forgetting it). Secondly, the implicit conversion from
NonNegInt means the programmer can just provide a regular integer value for parameter
j, instead of having to explicitly say
NonNegInt(1). Third, the implicit conversion from
Int means that
j can easily be used anywhere a regular
Int is used. Last, and very definitely not least, the fact that function
element requires a non-negative integer is obvious right in the function signature. There is no need for a programmer to guess whether
j can be negative, and no need for the author of
element to document that
j cannot be negative. Its type makes that completely clear.
In this post I’ve laid out some advantages of defining lightweight non-negative numeric types, in particular using
NonNegInt as a working example. Clearly, if you want to apply this idea, you’d want to also define
NonNegFloat and for that matter
PosLong, etc. Happy computing!