Dreaming of Ada Subtypes in Swift
Wishing Swift had Ada's subtype feature, and looking at what I can use in Swift instead to get a similar outcome.
I am working on an app where the model works on weeks of the year. I represent the week data as an array, with one entry for each week in the year.
This means I can either have an array of 52 elements or 53 elements (for a leap year).
So let's say I have a YearStore type:
struct YearStore {
private let year: Int
private let isLeapYear: Bool
private var weeks: [WeekRecord]
init(year: Int) {
self.year = year
self.isLeapYear = isLeapYear(year)
self.weeks = Array<WeekRecord>(repeating: WeekRecord.blank,
count: isLeapYear ? 53 : 52)
}
}
To access and mutate the week data, let's suppose I have methods like:
extension YearStore {
func record(for weekNumber: Int) -> WeekRecord { /// ... snip }
func set(record: WeekRecord for weekNumber: Int) throws { /// ...snip }
}
Every access to the underlying store of records, needs a runtime check for the index access (to ensure the weekNumber
is a valid value).
Given I know the exact range of valid values, I wanted to create a type for weekNumber
that only permits valid values at compile time.
This made me think back to the Ada programming language, which I was taught at university. Ada has a really nice feature for implementing such a type, namely subtypes (see also refinement type).
In Ada, I could define types such as:
subtype WeekNumber_Int is Integer range 1 .. 53;
Or perhaps even:
subtype WeekNumberForLeapYear_Int is Integer range 1 .. 53;
subtype WeekNumberForNormalYear_Int is Integer range 1 .. 52;
These subtypes are not mere aliases to Integers: it is illegal to use an Integer in the place of a WeekNumber_Int, and vice versa.
Swift does not currently support Ada style subtypes, but there are ways to get similar functionality:
- Define an enum with raw values in the allowed range of values.
- Use a Struct with validation logic in its initializer so that the values are within the legal range.
- Use a custom type with the
ExpressibleByIntegerLiteral
protocol. The type could then be initialized using integer literals while enforcing legal values. - Use a wrapper type around integers (or other underlying type). The wrapper type would have functionality for validation of legal values.
All of the above are viable, but feel very clunky compared to just being able to spin out an Ada-style subtype. Maybe we'll get this feature in Swift at some point in the future.
I have to admit I was a bit surprised that neither Swift nor Rust currently support Ada-style subtypes. It reminded me of my lecturers at university telling a skeptical student body that they were teaching us languages and technology that industry would one day catch up with, rather than the latest hotness of the day (C++ and Java at the time)...