Oops; sorry for missing that!
Some really cool ideas. This is a lot of fun.
As @Jon_Shier points out, there is not really a precedent for or* operations so it is probably not a very good name! Sorry! On the other hand, a throwing unwrapped()
on Optional
seems very natural to me, and if you said its been there since Swift 2, I would have believed you.
Because unwrapped()
feels like such an obvious and natural abstraction to me now, I think it can (and should?) be considered independently of making throws a bottom type that can be used with ??
. If I understand the issues correctly, I don't think that having this unwrapped function walls off this improvement to the type checker as a future feature, and it addresses an existential, day-to-day problem.
I like the nested name Missing
error the best. Great observation from Rob @mayoff that it's going to be generic and harder to catch. I kind of feel like in this case where you want to be real specific, though, the caller should be handling the optional differently, either with a tried and true guard let
or throwing a custom, remapped error that is more "catchable." Fortunately, though, you can still catch it if you have to.
The overload solution from @noremac is pretty great because it makes errors immediately useful to those just starting out. It encourages playgrounders to embrace error handling instead of try! or compact mapping away errors the name of, "hey, this is just test playground code" only to have it copy and pasted into production code and then shipped!
I do not understand what the term âbottom typeâ means, but if it turns throws
into being used at the end of ??
for nil coalescing, I support this, though maybe only if itâs inside an appropriate context, like only inside a do
block.
The bottom type concept, where Never
is an example, is how we can use preconditionFailure
or fatalError
to end a value-returning function without screwing up the type checker.
I think all the argument-specific versions of Optional.MissingValueError
should directly refine Error
, but of a general Optional
error.
protocol MissingOptionalValueError: Error {}
extension Optional {
struct MissingValueError: MissingOptionalValueError, CustomStringConvertible { /*...*/ }
//...
}
This way people who want per-Wrapped
and Wrapped
-agnostic handling of Optional
screw-ups can be happy.
Can throw
(or a new function that throws) be turned into a pseudo bottom type like this undefined<A>
generic function is in this StackOverflow response? I donât really understand how that works. What even is the return type of fatalError()
anyway?
@noreturn
is an old concept that got replaced by Never
in SE-0102. So fatalError
is now just a normal () -> Never
function.
If we have Never
as the bottom type (meaning Never
is a subtype of every type), we could replace the throw
with something similar to:
// Note that this declaration compiles succesfully today.
func throwOnly(_ error: Error) throws -> Never {
throw error
}
We can then do something like: value ?? throwOnly(error)
as Never
will be the subtype of Value
. Currently it doesn't work as Never
is unrelated to Value
(except for when Value
is Never
).
Not that I particularly like the concept of bottom type, though.
I think we need to make throw
an expression (currently itâs a statement) which should allow you to do a ?? throw b
for example.
We also need to figure out the type of throw
. Since throwing Never
(my throwOnly
) is apparently not coerced properly.
Right, we would also need to give it a Never
type if we decide to make Never
a bottom type.
Curious if people agree
// requires a small library change
let url = try URL(string: location).unwrapped() // throws Missing<URL> with FILE:LINE
let url = try URL(string: location).unwrapped(orThrows: InvalidURL(location))
and
// requires a deeper change to compiler
let url = URL(string: location) ?? throw InvalidURL(location)
Can live together? I think unwrapped()
or unwrap()
? is a nice API for optional.
One minor point which I'm not sure was mentioned, if we can make x ?? throw e
to work, I think it would be convenient if that expression didn't require prefixing it with try
, even if the throw
is technically buried inside the autoclosure
expression.
Paraphrasing freak4pc, I'm not a fan of optional ?? throw Error
and strongly disagree that it "makes sense" from a language feel perspective.
This operator is currently is always like "value or value", and used like let a = wrapped ?? value
or sometimes let b = wrapped ?? otherWrapped ?? value
. In those cases a
and b
always end up being assigned values.
A throw or call to fatalError
(etc) is an action not a value. Allowing Never
types in ??
would then permit it to mean "value otherwise action" sometimes. To me, this is a significant change to the mental model of this operator. Changes it negatively by making it less clear and more blurry, which distracts when reading (which sense of ??
is this?), and detracts from learning (which actions can go after ??
and why).
EDITS: completed after accidentally submitting mid-sentence :^/, corrected some unquoted code, added final sentence justifying my argument, reworded this list of edits.
As pointed out by others (such as Nevin, here), ??
can already mean âvalue otherwise actionâ, if you write a throwing expression with the appropriate type on the right-hand side of ??
. I've used a function like Nevin's throwError
in my projects in exactly this way.
Changing throw
to be an expression simply standardizes the spelling of that throwing expression. I don't know that doing so requires making Never
a bottom type, as opposed to just special casing the type of a throw
expression.
I don't think I have had a project where I haven't needed ?? {throw MyError.errorType}()
I fully agree with @Nevin's spelling of making throw act like a bottom type.
I would also like to see throws?
added to the language, which would act exactly like throws
except instead of requiring try
, the compiler would add an implicit try?
before any call to the function.
func myThrowingFunc() throws? -> Int {
//Do stuff
if problem {
throw MyError.errorType
}
return answer
}
This would say that there is throwing information, but you usually don't care about it
let x:Int? = myThrowingFunc() ///This has an implicit try? in front of it
But in cases where you actually do want it to throw, you would just call with the try
keyword, and it will use that instead of try?
let x:Int = try myThrowingFunc()
The end result is that you can provide throwing for those who want it, without burdening the API with it when it isn't useful most of the time.
As an example, I have a packrat parser where I use the throwing information while developing/debugging a parser ruleset, but in actual production I always use try?
because it lets me use if let
, which simplifies the algorithm quite a bit from do catch
, and the reason for the error doesn't matter... just that it failed.
I'd also like to see throws!
which is the same, but with trapping.
It would be great to have a way to say "I know you normally trap, but in this case, if you would trap, just give me a nil result or throw instead"...
Iâm not sure I understand what you mean, but the fact that youâre saying that something with throws!
could return nil
without crashing makes me inclined to dislike it, as it seems to run counter to the way the bang works for Optional
. If I am misunderstanding, could you maybe give an example of usage?
Overall, I think throws?
could be useful (like in the case you mentioned previously) but at the same time I wonder if it might be a sirenâs call for making poor design choices in cases that should normally throw.
throws!
would be just like throws?
except it has an implicit try!
in front of any call which isn't otherwise marked with try. Thus it will trap if you call it normally:
func myTrappingFunc() throws! -> Int {
guard !problem else {throw MyError.errorType}
return answer
}
let x = myTrappingFunc() ///This has an implicit try! and will trap if it throws
But if you want it to throw instead, you would put try
in front of it:
let x = try myTrappingFunc() ///Now it will throw instead of trap
Or if you wanted it to return an optional, and return nil when it throws:
let x = try? myTrappingFunc() ///Now it returns an optional instead
So both throws?
and throws!
work just like throws
, but have a default which allows them to act like the other types of error handling by default. You can override that default behavior by explicitly marking it.
As I understand it, making calls to a throwing function look different to calls to a non-throwing function was an intentional design point. This suggestion completely defeats that. Plus the return type of such functions is partially a lie; it jumps between to MyReturnType
when you prepend "try
" or MyReturnType?
without it. Transforming exception handling to nil
return via a decision at the caller's side was also a design point.
The desire to treat throw X
as an expression dovetails with other calls to make if
and switch
also be expressions, and definitely has my support.