So, what you’re saying is essentially equivalent to replacing Optional struct 
with an Optional protocol with a default AnyOptional struct implementation, 
right?
Having the bulk of compiler magic moved to a protocol was one of my proposed 
solutions, but it still isn’t optimal due to associated types and need for 
existential containers, that could seriously slow down the code, considering 
the heavy use of Optional.

> On May 1, 2017, at 12:34 AM, David Sweeris <daveswee...@mac.com> wrote:
> 
> 
>> On Apr 30, 2017, at 10:11, Gor Gyolchanyan via swift-evolution 
>> <swift-evolution@swift.org> wrote:
>> 
>> I’d like to suggest a bit of redesigning the Optional type and throwing 
>> functions to provide a single powerful and flexible mechanism for dealing 
>> with unexpected situations.
>> 
>> In short, The Optional would have an associated value of type Error added to 
>> its `none` case, which would describe the reason why the wrapped value is 
>> missing.
>> 
>> public enum Optional<Wrapped> {
>> 
>>   case .some(Wrapped)
>> 
>>   case .none(Error)
>> 
>> }
>> 
>> The Optional's ExpressibleByNilLiteral would initialize it with an error 
>> that corresponds to what is currently fatalError-ed as "unexpectedly found 
>> nil while unwrapping an Optional value".
>> 
>> The forced unwrapping operator (postfix `!`) would behave the same way as it 
>> does now, except in case of a fatal error it would print out the underlying 
>> error, instead of the aforementioned hard-coded string.
>> 
>> The optional chaining operator (postfix `?`) would behave the same way as it 
>> does now, except when it stops evaluating and returns the Optional, it would 
>> contain the error, returned by the sub-expression that failed to evaluate.
>> 
>> Any throwing function would be equivalent to a function that returns an 
>> Optional. If the function is declared as throwing and returning an Optional 
>> at the same time, it would be equivalent to a function returning an Optional 
>> Optional.
>> 
>> The if-let statement would bind the `let` variable to the wrapped value 
>> inside the "then" block and would bind it to the error in the "else" block. 
>> Chained else-if blocks would all be considered part of the overarching 
>> "else" block, so all of them would be able to access the error bound to the 
>> if-let name.
>> 
>> The guard-let and case-let statements are essentially just rewrites of 
>> if-let with some added logic.
>> 
>> The `try` keyword, applied to an optional would behave like this:
>> 
>> public func try<T>(_ optional: T?) throws -> T {
>>   guard let wrapped = optional else {
>>       throw wrapped // Remember, if-let, guard-let and case-let statements 
>> bind the let name to the error in case of a failure.
>>   }
>>   return wrapped
>> }
>> 
>> Multiple let bindings in a single if-let statement are essentially rewrites 
>> of a nested chain of if-let statements.
>> 
>> The `try` keyword applied to an optional would unwrap the value or throw the 
>> error.
>> The `try?` keyword applied to a throwing function call would cause any 
>> thrown errors to be caught and put into the returned Optional, instead of 
>> simply ignored.
>> The `try!` keyword applied to a throwing function call would behave as you'd 
>> expect: just like `try?` except immediately force-unwrapped.
>> 
>> A throwing function would be convertible to a non-throwing 
>> optional-returning function and vice versa.
>> This would allow making use of throwing functions when dealing with generics 
>> or protocols that allow arbitrary return types, without having to sacrifice 
>> the convenience of error-handling logic. Conversely, it would allow to write 
>> generic code that deals with any type of function without having to 
>> implement special cases for throwing functions. This means that the two 
>> function types would be interchangeable and one would be able to satisfy 
>> protocol requirements of the other. The `rethrows` idiom would then become a 
>> natural consequence of writing generic functions that may return optional 
>> and non-optional results just as well.
> 
> I forget the details, but IIRC throwing and optionals are intended to solve 
> different problems. Assuming that I actually am recalling correctly, I don't 
> think combining them would be a good idea.
> 
> That said, it seems like there ought to be a way to say something like:
> protocol HasErrorPayload {
>  associatedtype Payload
> }
> And then either (straw-man syntax):
> enum Optional<T> {
>  typealias Wrapped = T
>  typealias Error = (T: HasErrorPayload) ? T.Payload : Void
>  case some(Wrapped)
>  case none(Error)
> }
> 
> Or (more straw-man syntax):
> enum Optional<T> {
>  typealias Wrapped = T
>  associatedtype Error //means an extension will *always* be able to fill-in 
> the details
>  case some(Wrapped)
>  case none(Error)
> }
> extension Optional {
>  typealias Error = Void
> }
> extension Optional where Wrapped: HasErrorPayload {
>  typealias Error = Wrapped.Payload
> }
> 
> That way existing code would "Just Work" (I think) because we wouldn't have 
> to deal with the .none case suddenly having associated values, and types 
> which want that extra info have a way to provide it.
> 
> So I guess my opinion on the matter is that, like so many other type-related 
> suggestions, the best solution is to abstract it out to a general-purpose 
> generics enhancement.
> 
> - Dave Sweeris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to