[swift-evolution] guard let x = x

Nevin Brackett-Rozinsky nevin.brackettrozinsky at gmail.com
Tue Nov 1 15:28:17 CDT 2016


Bikeshedding here—is there any way we can make it look like this for enums?

if let str = unwrap json.string {
    // str is the value from the .string enum case
}

Essentially, the “unwrap” keyword would treat the last “path component” of
what follows as an enum case, and extract the associated value if it
matches.

This could even work with tuple destructuring, as in:

guard let (theCode, someMessage) = unwrap myResponse.contact else {
    return
}

And of course we could add a sugared form for optionals to allow the
simple, “if unwrap x { … }”.

I’m not sure if this is where we want to take the idea, it’s just something
I thought of.

If we did go that route, then we might need to disallow enums from having
properties with the same names as their cases (otherwise, eg. json.string
would refer to a property not a case).

On the other hand, a slightly different syntax would avoid that issue
entirely—for instance,

if let str = unwrap json(.string) { … }
or
if let str = unwrap(.string) json { … }

That last one could even be sugared further when shadowing is desired:

if unwrap(.string) json {
    // json is a String in here
}

Potentially worthwhile?

Nevin


On Tue, Nov 1, 2016 at 2:42 PM, Erica Sadun via swift-evolution <
swift-evolution at swift.org> wrote:

> With massive revisions. I've added Xiaodi Wu to author list. I've combined
> all unwrapping together. I've added a much more sensible approach (plus a
> bluesky one) for real world enumerations.
>
> Gist with live updates here: https://gist.github.com/erica/
> db9ce92b3d23cb20799460f603c0ae7c
>
> Send feedback, I will continue to revise.
>
> -- E
>
>
> Better Unwrapping
>
>    - Proposal: TBD
>    - Author: Erica Sadun <https://github.com/erica>, Chris Lattner
>    <https://github.com/lattner>, Xiaodi Wu <https://github.com/xwu> David
>    Goodine
>    - Status: TBD
>    - Review manager: TBD
>
>
> <https://gist.github.com/erica/db9ce92b3d23cb20799460f603c0ae7c#introduction>
> Introduction
>
> This proposal redesigns common unwrapping tasks:
>
>    - It introduces the unwrap keyword for optional values
>    - It re-architects guard case and if case grammar to support
>    unwrapping more complex enumerations by dropping the case keyword and
>    replacing = with ~=.
>    - It applies unwrap to non-optional values
>
> Swift Evolution threads:
>
>    - guard let x = x
>    <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/028440.html>
>    - Using a bind keyword
>    <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/thread.html>
>    .
>    - Fixing pattern matching grammar
>    <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
>
> <https://gist.github.com/erica/db9ce92b3d23cb20799460f603c0ae7c#motivation>
> Motivation
>
> Unwrapping values is one of the most common Swift tasks and it is
> unnecessarily complex. This proposal simplifies this process and enhances
> code safety and readability.
> <https://gist.github.com/erica/db9ce92b3d23cb20799460f603c0ae7c#optionals>
> Optionals
>
> Swift lacks a safe way to bind an optional to a shadowed same-name
> variable in condition clauses like those used in guard and if statements.
>
> Compare:
>
> guard let foo = foo else { ... } // redundantguard case let .some(foo) = foo else { ... } // overly complexguard unwrap foo else { ... } // simple
>
>
>    - Using "foo = foo" fails DRY principles
>    <https://en.wikipedia.org/wiki/Don%27t_repeat_yourself>.
>    - Using case let .some(foo) = foo or case .some(let foo) = foo fails KISS
>    principles <https://en.wikipedia.org/wiki/KISS_principle>
>
> unwrap guarantees that an unwrapped shadow uses the same name as the
> wrapped version. This ensures that a conditionally-bound item cannot
> accidentally shadow another symbol. It eliminates repetition and retains
> clarity. Further, the unwrap keyword is common, simple to understand, and
> easy to search for if Swift users are unfamiliar with it.
>
> In the rare case that the binding is variable, use the re-imagined var ~=
>  syntax
>
> <https://gist.github.com/erica/db9ce92b3d23cb20799460f603c0ae7c#general-enumerations>General
> Enumerations
>
> Swift's guard case and if case statements stand out for their unintuitive
> approach. They look like assignment statements but they are *not* assignment
> statements. This violates the principle of least astonishment
> <https://en.wikipedia.org/wiki/Principle_of_least_astonishment>. This
> presents difficulties for new language adopters by combining several
> concepts in a confusing form. They are arguably underutilized by language
> experts.
>
> The status quo for the = operator is iteratively built up in this fashion:
>
>    - = performs assignment
>    - let x = performs binding
>    - if let x = performs conditional binding on optionals
>    - if case .foo(let x) = and if case let .foo(x) = performs conditional
>    binding on enumerations *and* applies pattern matching
>
> Both guard case and if case statements perform simultaneous pattern
> matching and conditional binding. Here are examples demonstrating their use
> in current Swift:
>
> enum Result<T> { case success(T), error(Error) }
>
> // valid Swift
> guard case let .success(value) = result
>     else { ... }
>
> // valid Swift
> guard case .success(let value) = result
>     else { ... }
>
> The problems with guard case and if case include:
>
>    - The = operator looks like assignment and not like pattern matching (
>    ~=).
>    - The case layout is both too close to a switch's case but doesn't
>    follow its syntax. In switch, a case is followed by a colon, not an
>    equal sign.
>    - Using the case syntax is unneccessarily wordy. It incorporates case,
>    =, and optionally let/var assignments.
>
>
> <https://gist.github.com/erica/db9ce92b3d23cb20799460f603c0ae7c#indirect-and-direct-pattern-matching>Indirect
> and Direct Pattern Matching
>
> Swift uses two kinds of pattern matching.
>
> Indirect pattern matching such as the kind you see in switch and for statements
> receives an argument in from the statement structure. The argument is not
> mentioned directly in the case:
>
> switch value {
> case .foo(let x): ... use x ...
> ...
> }
>
> for case .foo(let x) in collection { ... }
>
> Direct pattern matching including guard/if statements and with the
> pattern matching operator place the argument to be matched to the right of
> an operator, either = or ~=. The argument is explicitly mentioned:
>
> if case .foo(let x) = value { ... use x ... }
> if 100...200 ~= value { ... }
>
> When using if case/guard case in the absence of conditional binding,
> statements duplicate basic pattern matching with less obvious semantics.
> These following two statements are functionally identical. The second uses
> an assignment operator and the case keyword.
>
> if range ~= myValue { ... } // simpler
> if case range = myValue { ... } // confusing
>
>
> <https://gist.github.com/erica/db9ce92b3d23cb20799460f603c0ae7c#detailed-design>Detailed
> Design
>
> This proposal introduces the unwrap keyword. The unwrap statement shadows
> an enumeration variable to an unwrapped version of the same type. Upon
> adopting this proposal the following statements produce equivalent behvior:
>
> // New unwrap keywordif unwrap myValue { ... }
> // Existing same-name shadowingif let myValue = myValue { ... }
> // Existing same-name pattern matching and conditional bindingif case .some(let myValue) = myValue { ... } // old grammarif case let .some(myValue) = myValue { ... } // old grammar// Proposed same-name pattern matching and conditional bindingif .some(let myValue) ~= myValue { ... } // new grammarif let .some(myValue) ~= myValue { ... } // new grammar
>
> In if case and guard case, this proposal drops the case keyword and
> replaces the equal sign with the pattern matching operator. The results
> look like this:
>
> guard let .success(value) ~= result else { ... }guard .success(let value) ~= result else { ... }if let .success(value) ~= result { ... }if .success(let value) ~= result { ... }guard let x? ~= anOptional else { ... }if let x? ~= anOptional { ... }
>
> Users may choose to use var instead of let to bind to a variable instead
> of a constant.
>
> In this update:
>
>    - The case keyword is subsumed into the (existing) pattern matching
>    operator
>    - The statements adopt the existing if-let and guard-let syntax,
>    including Optional syntactic sugar.
>
> if let x = anOptional { ... } // currentif case let x? = anOptional { ... } // current, would be removedif let x? ~= anOptional { ... } // proposed replacement for `if case`
>
> On adopting this syntax, the two identical range tests naturally unify to
> this single version:
>
> if range ~= myValue { ... } // beforeif case range = myValue { ... } // beforeif range ~= myValue { ... } // after
>
> Using pattern matching without conditional binding naturally simplifies to
> a standalone Boolean condition clause.
>
> <https://gist.github.com/erica/db9ce92b3d23cb20799460f603c0ae7c#unwrap-and-non-optionals>
> unwrap and Non-Optionals
>
> Real world Swift enumerations rarely follow the Optional pattern, which
> can be summed up like this:
>
> enum OptionalLike<T> { case aCaseWithOneAssociatedValue(T), anotherCase }
>
> They more typically look like this:
>
> // One generic, one known typeenum Result<Value> { case success(Value), failure(Error) }
> // Many cases of mixed typesenum JSON {
>     case string(String)
>     case number(Double)
>     case boolean(Bool)
>     case array([JSON])
>     case dictionary([String: JSON])
>     case null
> }
> // Multiple associated valuesenum Response { case contact(code: Int, message: String), failure }
>
> You can adapt the bind keyword to work with these real-world cases in one
> of two ways. The first way uses unwrapinstead of let or var. Here are a
> few varieties of how that call might look versus the proposed update for
> normal pattern matching:
>
> if unwrap .string(myString) ~= json { ... }if unwrap .contact(code, _) ~= response { ... }if unwrap .contact(code, var message) ~= response { ... }
> // vs proposedif let .string(myString) ~= json { ... }if var .string(myString) ~= json { ... }if .contact(let code, _) ~= response { ... }if .contact(let code, var message) ~= response { ... }
>
> Although slightly wordier than let and var, the unwrap solution offers
> advantages:
>
>    - It enhances readability. If the goal is to unwrap an embedded value,
>    unwrap uses a more appropriate term.
>    - It establishes *one* place for the keyword to live instead of the
>    "does it go inside or outside" status quo. A consistent place means
>    prettier code.
>    - As shown in these examples, it can easily be adapted for variable
>    binding. If you want to override the let behavior, you can insert a var
>    inside the parentheses.
>
> A second, riskier, cleaner approach looks like this. It assumes the
> compiler can pick up and shadow using either the associated value labels
> (preferred) or the enumeration name (as you'd see with String raw value
> enumerations):
>
> if unwrap .contact ~= response {
>    ... some compiler magic picks up on the `code` and `message` labels
>    used for initialization, so the bound variables are `code`
>    and `message`
> }
> if unwrap .string ~= json {
>    ... use `string` here (same name as enum case) because
>    no label was used to define the associated value
> }
>
>
> <https://gist.github.com/erica/db9ce92b3d23cb20799460f603c0ae7c#impact-on-existing-code>Impact
> on Existing Code
>
> This proposal is breaking and would require migration.
>
> <https://gist.github.com/erica/db9ce92b3d23cb20799460f603c0ae7c#alternatives-considered>Alternatives
> Considered
>
>    - Leaving the grammar as-is, albeit confusing
>    - Retaining case and replacing the equal sign with ~= (pattern
>    matching) or : (to match the switch statement).
>
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161101/ae02dc17/attachment.html>


More information about the swift-evolution mailing list