(Seems like this is something that ought to have been pitched before? But I found no thread...)
For many years I've been wanting to be able to have a convenient way of "inferring" / "inheriting" / "following" / "reverse-forwarding" the default value of a parameter, either to init or other function.
Imagine we have syntax for conveniently working with Date
, like so 30.years.ago
, look at this simple struct Person
:
public struct Person {
public let firstName: String
public let lastName: String
public let dateOfBirth: Date
public init(
firstName: String,
lastName: String,
dateOfBirth: Date = 30.years.ago
) {
self.firstName = firstName
self.lastName = lastName
self.dateOfBirth = dateOfBirth
}
public static func unknown(
dateOfBirth: Date = 30.years.ago
) -> Self {
Self.init(firstName: "Jane", lastName: "Doe", dateOfBirth: dateOfBirth)
}
}
See how we are duplicating the value 30
, it is specified twice. But imagine we semantically mean to use the same value. We solve this ofc either by doing this:
public struct Person1 {
...
public static let defaultDateOfBirth: Date = 30.years.ago
public init(
firstName: String,
lastName: String,
dateOfBirth: Date = Self.defaultDateOfBirth
) {
...
}
public static func unknown(
dateOfBirth: Date = Self.defaultDateOfBirth
) -> Self {
Self.init(firstName: "Jane", lastName: "Doe", dateOfBirth: dateOfBirth)
}
}
Or by doing this:
extension Date {
public static let defaultDateOfBirth: Self = 30.years.ago
}
public struct Person2 {
...
public init(
firstName: String,
lastName: String,
dateOfBirth: Date = .defaultDateOfBirth
) {
...
}
public static func unknown(
dateOfBirth: Date = .defaultDateOfBirth
) -> Self {
Self.init(firstName: "Jane", lastName: "Doe", dateOfBirth: dateOfBirth)
}
}
This is ofc a very very simple scenario with just one default value for a parameters, but imagine we have 3 or 4 or more even. Both version struct Person1
and struct Person2
solution have their own drawback.
The problem with struct Person1
is that we are "polluting" the struct with static let
s for the default values. The problem with struct Person2
is that we are "polluting" the type Date
with domain specific values. And since both init
and static func unknown
are public
the extension inside Date
need also be public
(otherwise this solution would not have been that bad).
Default values for parameters are awesome when used in just one place, because the do not have any of struct Person1
nor struct Person2
s problem, the default value is just declare with = .defaultValue
which is perfect, exactly what we want, but as soon as we need to reference semantically this default value in more than one place, we must retort to any of the two bad solutions.
But what if, we could with some compiler magic get this syntax possible?
public struct Person3 {
...
public init(
firstName: String,
lastName: String,
dateOfBirth: Date = 30.years.ago
) {
...
}
public static func unknown(
dateOfBirth: Date = .inferred // not possible today, and this is what I pitch!
) -> Self {
Self.init(firstName: "Jane", lastName: "Doe", dateOfBirth: dateOfBirth)
}
}
In struct Person3
's func unknown
we spell out the default argument as .inferred
, which tells Swift that most likely during compilation (I'm not a compiler engineer, but this seams reasonable?), replace .inferred
with = 30.years.ago
from the function (init) called!
And this would also work in several steps/hops, so this would also work:
public struct Person3 {
...
public init(
firstName: String,
lastName: String,
dateOfBirth: Date = 30.years.ago
) {
...
}
public static func doe(
firstName: String,
dateOfBirth: Date = .inferred
) -> Self {
Self.init(firstName: firstName, lastName: "Doe", dateOfBirth: dateOfBirth)
}
public func unknown(
isMale: Bool,
dateOfBirth: Date = .inferred
) -> Self {
Self.doe(firstName: isMale ? "John" : "Jane", dateOfBirth: dateOfBirth)
}
}
What do you think? Have you found yourself in need of this ever?