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)
}
Ā·Ā·Ā·
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.
unwrap.md Ā· GitHub
--
Brent Royal-Gordon
Architechies