> On Mar 7, 2017, at 12:14 PM, Erica Sadun via swift-evolution 
> <[email protected]> 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.

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
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to