> 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