> On Mar 7, 2017, at 8:59 PM, Brent Royal-Gordon via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
>> On Mar 7, 2017, at 12:14 PM, Erica Sadun via swift-evolution 
>> <swift-evolution@swift.org> wrote:
>> 
>> Because of that, I'm going to start over here, hopefully pulling in all the 
>> details
>> and allowing the community to provide feedback and direction. The following 
>> gist is an amalgam of work I was discussing with Xiaodi Wu, Chris Lattner, 
>> and
>> David Goodine.
>> 
>> https://gist.github.com/erica/aea6a1c55e9e92f843f92e2b16879b0f
> 
> Treating the things separately:
> 
> 1. Introduce an `unwrap` keyword
> 
> I'm really not convinced this pulls its own weight. Without the `let`, it 
> doesn't make the fact that it's shadowing the original (and thus that you 
> cannot modify it) clear; with the `let`, it introduces a new keyword people 
> need to learn for the sake of eliding a repeated variable name.

If Swift supported `if inout x = x { … }`, then `unwrap` could have much more 
reasonable semantics.

> 
> In the document, you state that `unwrap` "simplifies the status quo and 
> eleminates unintended shadows", but that's not true, because the existing 
> syntax will continue to exist and be supported. Unless we warn about *any* 
> shadowing in an `if let` or `if case`, it will still be possible to 
> accidentally shadow variables using these declarations.
> 
> 2. Introduce an `Unwrappable` protocol
> 
> I like the idea, but I would use a slightly different design which offers 
> more features and lifts this from "bag of syntax" territory into representing 
> a discrete semantic. This particular design includes several elements which 
> depend on other proposed features:
> 
>       /// Conforming types wrap another type, creating a supertype which may 
> or may not 
>       /// contain the `Wrapped` type.
>       /// 
>       /// `Wrapper` types may use the `!` operator to unconditionally access 
> the wrapped 
>       /// value or the `if let` and `guard let` statements to conditionally 
> access it. Additionally, 
>       /// `Wrapped` values will be automatically converted to the 
> `Wrapper`-conforming type 
>       /// as needed, and the `is`, `as`, `as?`, and `as!` operators will 
> treat the `Wrapped` type 
>       /// as a subtype of the `Wrapper`-conforming type.
>       protocol Wrapper {
>               /// The type that this value wraps.
>               associatedtype Wrapped
>               
>               /// The type of error, if any, thrown when a non-wrapped value 
> is unwrapped.
>               associatedtype UnwrappingError: Error = Never
>               
>               /// Creates an instance of `Self` which wraps the `Wrapped` 
> value.
>               /// 
>               /// You can call this initializer explicitly, but Swift will 
> also insert implicit calls when 
>               /// upcasting from `Wrapped` to `Self`.
>               init(_ wrapped: Wrapped)
>               
>               /// Returns `true` if `Self` contains an instance of `Wrapped` 
> which can be accessed 
>               /// by calling `unwrapped`.
>               var isWrapped: Bool { get }
>               
>               /// Accesses the `Wrapped` value within this instance.
>               /// 
>               /// If `isWrapped` is `true`, this property will always return 
> an instance. If it is `false`, this property 
>               /// will throw an instance of `UnwrappingError`, or trap if 
> `UnwrappingError` is `Never`.
>               var unwrapped: Wrapped { get throws<UnwrappingError> }
>               
>               /// Accesses the `Wrapped` value within this instance, possibly 
> skipping safety checks.
>               /// 
>               /// - Precondition: `isWrapped` is `true`.
>               var unsafelyUnwrapped: Wrapped { get }
>       }
>       
>       extension Wrapper {
>               // Default implementation of `unsafelyUnwrapped` just calls 
> `unwrapped`.
>               var unsafelyUnwrapped: Wrapped {
>                       return try! unwrapped
>               }
>       }
> 
> The defaulting of `WrappingError` to `Never` means the error-emitting aspects 
> of this design are additive and can be introduced later, once the necessary 
> supporting features are introduced. The use of separate `isWrapped` and 
> `unwrapped` properties means that `unwrapped` can implement an appropriate 
> behavior on unwrapping failure, instead of being forced to return `nil`.
> 
> (An alternative design would have `wrapped: Wrapped? { get }` and `unwrapped: 
> Wrapped { get throws<UnwrappingError> }` properties, instead of `isWrapped` 
> and `unwrapped`.)
> 
> In this model, your example of:
> 
>       let value = try unwrap myResult // throws on `failure`
> 
> Would instead be:
> 
>       let value = try myResult! // throws on `failure`
> 
> (Actually, I'm not sure why you said this would be `unwrap`—it's not 
> shadowing `myResult`, is it?)
> 
> Theoretically, this exact design—or something close to it—could be used to 
> implement subtyping:
> 
>       extension Int16: Wrapper {
>               typealias Wrapped = Int8
>               
>               init(_ wrapped: Int8) {
>                       self.init(exactly: wrapped)!
>               }
>               
>               var isWrapped: Bool {
>                       return Self(exactly: Int8.min)...Self(exactly: 
> Int8.max).contains(self)
>               }
>               
>               var unwrapped: Int8 {
>                       return Self(exactly: self)!
>               }
>       }
> 
> But this would imply that you could not only say `myInt8` where an `Int16` 
> was needed, but also that you could write `myInt16!` where an `Int8` was 
> needed. I'm not sure we want to overload force unwrapping like that. One 
> possibility is that unwrapping is a refinement of subtyping:
> 
>       // `Downcastable` contains the actual conversion and subtyping logic. 
> Conforming to 
>       // `Downcastable` gets you `is`, `as`, `as?`, and `as!` support; it 
> also lets you use an 
>       // instance of `Subtype` in contexts which want a `Supertype`.
>       protocol Downcastable {
>               associatedtype Subtype
>               associatedtype DowncastingError: Error = Never
>               
>               init(upcasting subvalue: Subtype)
>               
>               var canDowncast: Bool { get }
>               
>               var downcasted: Subtype { get throws<DowncastingError> }
>               
>               var unsafelyDowncasted: Subtype { get }
>       }
>       
>       // Unwrappable refines Downcastable, providing access to `!`, `if let`, 
> etc.
>       protocol Unwrappable: Downcastable {}
>       extension Unwrappable {
>               var unsafelyUnwrapped: Subtype { return unsafelyDowncasted }
>       }
> 
> That would allow you to have conversions between `Int8` and `Int16`, but not 
> to use `!` on an `Int16`.
> 
> 3. Apply `unwrap` to non-`Optional` values, and
> 4. Extend `for` and `switch`
> 
> These are pretty straightforward ramifications of having both `unwrap` and 
> `Unwrappable`. I don't like `unwrap`, but if we *do* add it, it should 
> certainly do this.
> 
> 5. Fix Pattern Match Binding
> 
> The `case let .someCase(x, y)` syntax is really convenient when there are a 
> lot of variables to bind. I would suggest a fairly narrow warning: If you use 
> a leading `let`, and some—but not all—of the variables bound by the pattern 
> are shadowing, emit a warning. That would solve the `case let .two(newValue, 
> oldValue)`-where-`oldValue`-should-be-a-match problem.
> 
> 6. Simplify Complex Binding
> 
> I'm not convinced by this. The `case` keyword provides a strong link between 
> `if case` and `switch`/`case`; the `~=` operator doesn't do this. Unless we 
> wanted to redesign `switch`/`case` with matching ergonomics—which, uh, we 
> don't:
> 
>       switch value {
>       ~ .foo(let x): 
>               ...use x...
>       ...
>       }
> 
> —I don't think we should go in this direction. `for case` also has similar 
> concerns.
> 
> I think we'd be better off replacing the `~=` operator with something more 
> memorable. For instance:
> 
>       extension Range {
>               public func matches(_ value: Bound) -> Bool {
>                       return contains(value)
>               }
>       }
> 
> Or:
> 
>       public func isMatch<Bound: Comparable>(_ value: Bound, toCase pattern: 
> Range<Bound>) -> Bool {
>               return pattern.contains(value)
>       }
> 
> -- 
> Brent Royal-Gordon
> Architechies
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

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

Reply via email to