Generics in Swift currently allows for you to define what type a method, function, or other type will use. This works great in most cases, but every once in a while I wish I could define a static value for a type that way. Here are a few instances that I have run into that I have wanted this for, or examples I have come up with:
Examples
Validates Types
Sometimes you might run into a case where you have a type with a String property, but the value of the property should only be allowed to have n characters. I personally had this when creating a client for the PayPal REST API. They have a lot of object string properties with a maximum length. I managed to create a type that wrapped the value type with a certain validation type, so setting the property would fail if you gave it an invalid value (GitHub - skelpo/Failable: Property validation for constraining the valid values of a type). You might declare the property like this:
let name: Failable<String, Length256Validator> = try Failable("My Valid Name")
The problem with this is that you have to create a separate Length<N>Validator type for each length. It would be much easier if we could just pass the length in as a parameter to a generic LengthValidator type:
let name: Failable<String, LengthValidator<256>> = try Failable("My Valid Name")
Formatted Dates
Sometimes you have an app that uses a certain date encoding for some of your date properties when they are encoded/decoded. If you want to use synthesized encoder/decoder methods, you will have to use a custom type that wraps that actual date because JSONEncoder.date(Decoder|Encoding)Strategy changes the encoding for all dates. If you wanted date with the yyyy-MM-dd format, you might create a TimelessDate type and if you want the yyyy-MM format you might create YearMonthDate type.
Now the only difference between theses types is the date encoding format, so if instead of creating two types we could create one type that takes in a generic value, you only have one type to deal with:
typealias TimelessDate = FormattedDate<"yyyy-MM-dd">
typealias YearMonthDate = FormattedDate<"yyyy-MM">
public struct FormattedDate<format: String>: Codable {
internal static let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = format
return formatter
}()
public var date: Date
public init(_ date: Date) {
self.date = date
}
public init?(string: String) {
guard let date = Self.formatter.date(from: string) else { return nil }
self.date = date
}
public init(from decoder: Decoder)throws {
let string = try decoder.singleValueContainer().decode(String.self)
guard let date = Self.formatter.date(from: string) else {
throw DecodingError.typeMismatch(
Date.self,
DecodingError.Context(codingPath: [], debugDescription: "Cannot get date from string `\(string)`")
)
}
self.date = date
}
public func encode(to encoder: Encoder)throws {
var container = encoder.singleValueContainer()
let string = Self.formatter.string(from: date)
try container.encode(string)
}
}
Fixed Size Arrays
A FixedSizeArray type could be added to the standard library that has a generic Element type and a size value with the size of the array:
FixedSizeArray<Int, 42>
In Other Languages
The closest thing to this feature are templates in C++ which I happened to run across one day when reading this: https://learnxinyminutes.com/docs/c++/
Related Posts
- [Planning][Request] "constexpr" for Swift 5
- Compile-Time Constant Expressions for Swift
- Proposal: Compile-time parameters
Notes and Feedback
I personally don't have the time or knowledge to implement this myself at the moment, so I can't create an actual evolution pitch, but I thought I'd throw this out so it can either be placed on the table or killed outright
. Thoughts?