To provide more color here, I have a services layer that does "stuff" on behalf of a user (think like network requests, etc.) So there is a lot of
func thing(argumentForThing: Int, user: User) { ... }
Now 90% of the time, the appropriate user to pass here is some global variable (e.g. the user we logged in as, or some credentials stored in Keychain, etc). But depending on the state of the application there may be no such user or credentials if you have not logged in, and in that case we want a runtime error if you somehow tried to perform an operation. Additionally, in various testing workflows we might want to use some hardcoded test user, distinct from what username and password you filled out in the application's UX.
What falls out of all this is some kind of composing pattern, such as
//Get some user we're logged in as, or throw an error if we're not logged in
func getCurrentUser() throws -> User { ... }
func delete(child: Child, user: User = try getCurrentUser()) { ... }
func delete(parent: Parent, user: User = try getCurrentUser()) {let _ = parent.children.map{delete(child: $0)}}}}
From UI code this might be called as
@IBAction func deleteParent() {
do {
delete(parent: currentParent)
}
catch {
//surface error to user
}
}
Meanwhile in a unit test we do
func testDeleteParent() throws {
let p = Parent(forTesting: true)
delete(parent: p, user: testUser)
}
func testDeleteChild() throws {
let p = Child(forTesting: true)
delete(child: p, user: testUser)
}
If we assume we can delete parents, children, aunts, uncles, brothers, and sisters. And beyond deleting we can create, edit, send invoices to, disown, mail birthday cards to and so forth, and these operations compose in strange ways (if we disown a relative, we disown the relatives living in the same household, but not e.g. their adult children). What we have now is less of a one-liner for one function and more of a function prelude, across a very large number of functions.
Why is it undesirable to allow an Optional to be passed to that parameter?
The obvious meaning of operation(argument: p, user: nil)
is to perform operation without any user credentials. That is probably nonsensical for most operations in my motivating case (if no credentials are required the operation would not take the argument), but in cases where an operation legitimately sometimes takes credentials and sometimes not, we expect the operation(argument: p, user: nil)
to do the operation without credentials but in fact the one-liner will try to find some credentials from the current session to perform the operation. This is not what one expects.
isn’t it far more clear to write out the two overloads manually than to create a design where throws means “not throwing unless defaulted”?
Yes, it is more clear. It is also far more tedious, and Swift lacks a preprocessor to manage this for me across the volume of functions involved.