[Pitch] Guarding on enum values


(Andrew Duncan) #1

Motivation

The guard statement rescues us from the pyramid of doom, and lets our code hug the left margin more... if the failure case is false or nil. I'd like to guard against specific values in an enum, and get the same flattening of code flow. This generalizes Swift’s convenient handling of Optionals. (I have a hazy perception that this was mooted for 1.x, but can’t remember where I read it.)

The goal is to make error-handling by result-returning smoother. (I need say no more about side-effect NSError** parameters.) Consider everyone’s favorite enum example:

    enum Result {
    case .Fail(String) // Error message
    case .Succeed(MyType) // Something we use.
    }
    
The Pitch (to avoid TL;DR)

I'd like to use something like this:

    guard case let .Succeed(m) = returnsResult() else {
         return it
    }
    // Can safely use m, otherwise Result is passed back the call stack.

Remarks

When I'm getting back a returned Result I want to bail immediately and propagate (return) the .Fail value, but to use the Succeed value. On the left margin, not in a welter of braces. The naive way, which at least has the value of directness, is:

    let r = returnsResult()
    let m:MyType
    switch r {
    case .OK(let dummy): m = dummy
    default: return r
    }
    // Can use m safely here.
    
This is bulky and leaves the value r cluttering the rest of the method. Not to mention the terrible hanging right-brace.

We can do better:

    let r = returnsResult()
    guard case let .Succeed(m) = r else {
        return r
    }
    // m may safely graze.
    // r is mentioned *three* times, and is still hanging around.

Finally we see the gold of El Dorado:

    guard case let .Succeed(m) = returnsResult() else {
         return it
    }
    // m is in scope, no other cruft.

No repeated identifiers, no leftover debris.


(Brent Royal-Gordon) #2

   guard case let .Succeed(m) = returnsResult() else {
        return it
   }
   // Can safely use m, otherwise Result is passed back the call stack.

I didn't understand what you wanted to begin with, so to summarize: you want to be able to bind the return value of `returnsResult()` to a constant on the `else` branch if the pattern doesn't match.

I definitely see the use case here, but I can't say I like the implicit use of `it`. If we did something like this, I would prefer it be done by decorating the `else`:

  guard case let .Succeed(m) = returnsResult() else let r {
    return r
  }

However, I'm honestly not sure that's much less burdensome than this:

  let r = returnsResult()
  guard case let .Succeed(m) = r else {
    return r
  }

It *is* a line less, and a constant less, but it also means adding a new and slightly funky syntax to the language. I'm just not sure it's worth it.

···

--
Brent Royal-Gordon
Architechies


(Step C) #3

What about an enumeration with three cases?

For example:

enum Result {
case Success
case Failure
case Cancelled
}

···

On Dec 22, 2015, at 11:58 PM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

Motivation

The guard statement rescues us from the pyramid of doom, and lets our code hug the left margin more... if the failure case is false or nil. I'd like to guard against specific values in an enum, and get the same flattening of code flow. This generalizes Swift’s convenient handling of Optionals. (I have a hazy perception that this was mooted for 1.x, but can’t remember where I read it.)

The goal is to make error-handling by result-returning smoother. (I need say no more about side-effect NSError** parameters.) Consider everyone’s favorite enum example:

   enum Result {
   case .Fail(String) // Error message
   case .Succeed(MyType) // Something we use.
   }

The Pitch (to avoid TL;DR)

I'd like to use something like this:

   guard case let .Succeed(m) = returnsResult() else {
        return it
   }
   // Can safely use m, otherwise Result is passed back the call stack.


(Félix Cloutier) #4

I feel exactly like Brent.

Félix

···

Le 23 déc. 2015 à 04:15:24, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

  guard case let .Succeed(m) = returnsResult() else {
       return it
  }
  // Can safely use m, otherwise Result is passed back the call stack.

I didn't understand what you wanted to begin with, so to summarize: you want to be able to bind the return value of `returnsResult()` to a constant on the `else` branch if the pattern doesn't match.

I definitely see the use case here, but I can't say I like the implicit use of `it`. If we did something like this, I would prefer it be done by decorating the `else`:

  guard case let .Succeed(m) = returnsResult() else let r {
    return r
  }

However, I'm honestly not sure that's much less burdensome than this:

  let r = returnsResult()
  guard case let .Succeed(m) = r else {
    return r
  }

It *is* a line less, and a constant less, but it also means adding a new and slightly funky syntax to the language. I'm just not sure it's worth it.

--
Brent Royal-Gordon
Architechies

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


(Brent Royal-Gordon) #5

What about an enumeration with three cases?

For example:

enum Result {
case Success
case Failure
case Cancelled
}

What about it? The case permitted by the `guard` continues through the surrounding scope; the other cases are handled by the `else` block.

···

--
Brent Royal-Gordon
Architechies


(Andrew Duncan) #6

In fact, I feel the same way too. I have definite views about indefinite pronouns. When I am teaching, I studiously avoid “it”, “this”, and “that”: at any given instant half the students have wandering minds, and if they miss the referent, they get lost. My old HyperTalk habits must be resurfacing with “it”. :slight_smile:

I still think the use case is valuable as a (natural IMHO) generalization of guard, and feel the annoyance of having the bound variable show up three times and outlast the guard, when I don’t want to use or even see it. Brent’s suggestion removes the second objection and alleviates the first; I’ll see that, but ask if we can raise it. The pitch is:

    guard case let .Succeed(m) = returnsResult() else let r {
        return r
    }

Improvement! The question is: can we reduce this by one or two ‘r’s?

···

On 23 Dec, 2015, at 6:59, Félix Cloutier <felixcca@yahoo.ca> wrote:

I feel exactly like Brent.

Félix

Le 23 déc. 2015 à 04:15:24, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

guard case let .Succeed(m) = returnsResult() else {
      return it
}
// Can safely use m, otherwise Result is passed back the call stack.

I didn't understand what you wanted to begin with, so to summarize: you want to be able to bind the return value of `returnsResult()` to a constant on the `else` branch if the pattern doesn't match.

I definitely see the use case here, but I can't say I like the implicit use of `it`. If we did something like this, I would prefer it be done by decorating the `else`:

  guard case let .Succeed(m) = returnsResult() else let r {
    return r
  }

However, I'm honestly not sure that's much less burdensome than this:

  let r = returnsResult()
  guard case let .Succeed(m) = r else {
    return r
  }

It *is* a line less, and a constant less, but it also means adding a new and slightly funky syntax to the language. I'm just not sure it's worth it.

--
Brent Royal-Gordon
Architechies

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


(Joe Groff) #7

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
       return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.

-Joe

···

On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

In fact, I feel the same way too. I have definite views about indefinite pronouns. When I am teaching, I studiously avoid “it”, “this”, and “that”: at any given instant half the students have wandering minds, and if they miss the referent, they get lost. My old HyperTalk habits must be resurfacing with “it”. :slight_smile:

I still think the use case is valuable as a (natural IMHO) generalization of guard, and feel the annoyance of having the bound variable show up three times and outlast the guard, when I don’t want to use or even see it. Brent’s suggestion removes the second objection and alleviates the first; I’ll see that, but ask if we can raise it. The pitch is:

   guard case let .Succeed(m) = returnsResult() else let r {
       return r
   }

Improvement! The question is: can we reduce this by one or two ‘r’s?


(Brent Royal-Gordon) #8

I thought a bit about that. Would it make sense to support multiple else blocks?

  guard case let .Succeed(m) = returnsResult()
  else if case .Failure("Temporary Failure") {
    return retry()
  }
  else case let .Failure(r) {
    return r
  }

···

On Dec 23, 2015, at 3:35 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

In fact, I feel the same way too. I have definite views about indefinite pronouns. When I am teaching, I studiously avoid “it”, “this”, and “that”: at any given instant half the students have wandering minds, and if they miss the referent, they get lost. My old HyperTalk habits must be resurfacing with “it”. :slight_smile:

I still think the use case is valuable as a (natural IMHO) generalization of guard, and feel the annoyance of having the bound variable show up three times and outlast the guard, when I don’t want to use or even see it. Brent’s suggestion removes the second objection and alleviates the first; I’ll see that, but ask if we can raise it. The pitch is:

  guard case let .Succeed(m) = returnsResult() else let r {
      return r
  }

Improvement! The question is: can we reduce this by one or two ‘r’s?

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
      return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.

--
Brent Royal-Gordon
Architechies


(Andrew Duncan) #9

More progress! This sounds good, but it looks like what you intend is for r to be the error message in the Result enum type.

enum Result {
case .Fail(String) // Error message
case .Succeed(MyType) // Something to work with
}

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
      return r // Looks like r is bound to the error String.
               // But maybe you meant r = the entire returnsResult() result.
}

The sort of message-passing error-handling I have in mind is where each method in the call chain returns a full Result enum and each stage checks it for Succeed/Fail, and immediately bails on Fail, returning (propagating) the Result. To be sure, this is sort of what exceptions do under the hood anyway.

My use-case is a recursive descent parser that I want to bail when a syntax error is found. This could happen way deep in the stack of calls. If I consistently return a .Fail(ErrorCode) or .Succeed(ASTNode) from each method, I just pass on the Result in case of .Fail, or use it in case of .Succeed.

···

On 23 Dec, 2015, at 15:35, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
      return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.

-Joe


(Lily Ballard) #10

Cute, but it seems a little confusing. Personally, I'd rather just stick with a normal switch in cases like that:

let m: MyType
switch returnsResult() {
case let .Succeed(m_): m = m_
case let .Failure(r): return r
}

-Kevin

···

On Wed, Dec 23, 2015, at 03:35 PM, Joe Groff via swift-evolution wrote:

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
       return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.


(Joe Groff) #11

More progress! This sounds good, but it looks like what you intend is for r to be the error message in the Result enum type.

enum Result {
case .Fail(String) // Error message
case .Succeed(MyType) // Something to work with
}

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
     return r // Looks like r is bound to the error String.
              // But maybe you meant r = the entire returnsResult() result.
}

I see. If it's an arbitrary pattern, you can match 'case let r' to bind the entire value instead of picking out the payload of the other case. That would still be exhaustive.

-Joe

···

On Dec 23, 2015, at 3:56 PM, Andrew Duncan <andrewzboard@gmail.com> wrote:

The sort of message-passing error-handling I have in mind is where each method in the call chain returns a full Result enum and each stage checks it for Succeed/Fail, and immediately bails on Fail, returning (propagating) the Result. To be sure, this is sort of what exceptions do under the hood anyway.

My use-case is a recursive descent parser that I want to bail when a syntax error is found. This could happen way deep in the stack of calls. If I consistently return a .Fail(ErrorCode) or .Succeed(ASTNode) from each method, I just pass on the Result in case of .Fail, or use it in case of .Succeed.

On 23 Dec, 2015, at 15:35, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
     return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.

-Joe


(Andrew Duncan) #12

Yes, which would revert to Brent’s suggestion. But you have generalized it in a very compatible way.

As I read somewhere, improving programming languages comes from removing limitations rather than adding features. I intend for this Pitch to be the former, although it does kind of look like the latter.

···

On 23 Dec, 2015, at 15:58, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 3:56 PM, Andrew Duncan <andrewzboard@gmail.com> wrote:

More progress! This sounds good, but it looks like what you intend is for r to be the error message in the Result enum type.

enum Result {
case .Fail(String) // Error message
case .Succeed(MyType) // Something to work with
}

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
    return r // Looks like r is bound to the error String.
             // But maybe you meant r = the entire returnsResult() result.
}

I see. If it's an arbitrary pattern, you can match 'case let r' to bind the entire value instead of picking out the payload of the other case. That would still be exhaustive.

-Joe

The sort of message-passing error-handling I have in mind is where each method in the call chain returns a full Result enum and each stage checks it for Succeed/Fail, and immediately bails on Fail, returning (propagating) the Result. To be sure, this is sort of what exceptions do under the hood anyway.

My use-case is a recursive descent parser that I want to bail when a syntax error is found. This could happen way deep in the stack of calls. If I consistently return a .Fail(ErrorCode) or .Succeed(ASTNode) from each method, I just pass on the Result in case of .Fail, or use it in case of .Succeed.

On 23 Dec, 2015, at 15:35, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
    return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.

-Joe


(Thorsten Seitz) #13

What do you think of

guard let r = returnsResult(), case let .Succeed(m) = r else {
        return r
}

Which binds r only within the scope of the guard as desired.

Written in multiple lines

guard
        let r = returnsResult(),
        case let .Succeed(m) = r
else {
        return r
}

-Thorsten

···

Am 24.12.2015 um 01:02 schrieb Andrew Duncan via swift-evolution <swift-evolution@swift.org>:

Yes, which would revert to Brent’s suggestion. But you have generalized it in a very compatible way.

As I read somewhere, improving programming languages comes from removing limitations rather than adding features. I intend for this Pitch to be the former, although it does kind of look like the latter.

On 23 Dec, 2015, at 15:58, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 3:56 PM, Andrew Duncan <andrewzboard@gmail.com> wrote:

More progress! This sounds good, but it looks like what you intend is for r to be the error message in the Result enum type.

enum Result {
case .Fail(String) // Error message
case .Succeed(MyType) // Something to work with
}

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
   return r // Looks like r is bound to the error String.
            // But maybe you meant r = the entire returnsResult() result.
}

I see. If it's an arbitrary pattern, you can match 'case let r' to bind the entire value instead of picking out the payload of the other case. That would still be exhaustive.

-Joe

The sort of message-passing error-handling I have in mind is where each method in the call chain returns a full Result enum and each stage checks it for Succeed/Fail, and immediately bails on Fail, returning (propagating) the Result. To be sure, this is sort of what exceptions do under the hood anyway.

My use-case is a recursive descent parser that I want to bail when a syntax error is found. This could happen way deep in the stack of calls. If I consistently return a .Fail(ErrorCode) or .Succeed(ASTNode) from each method, I just pass on the Result in case of .Fail, or use it in case of .Succeed.

On 23 Dec, 2015, at 15:35, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
   return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.

-Joe

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


(Félix Cloutier) #14

I like that it's consistent with the if syntax (even though I don't really like the if syntax) and that there's no dangling parts after the else.

Félix

···

Le 24 déc. 2015 à 06:29:17, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> a écrit :

What do you think of

guard let r = returnsResult(), case let .Succeed(m) = r else {
       return r
}

Which binds r only within the scope of the guard as desired.

Written in multiple lines

guard
       let r = returnsResult(),
       case let .Succeed(m) = r
else {
       return r
}

-Thorsten

Am 24.12.2015 um 01:02 schrieb Andrew Duncan via swift-evolution <swift-evolution@swift.org>:

Yes, which would revert to Brent’s suggestion. But you have generalized it in a very compatible way.

As I read somewhere, improving programming languages comes from removing limitations rather than adding features. I intend for this Pitch to be the former, although it does kind of look like the latter.

On 23 Dec, 2015, at 15:58, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 3:56 PM, Andrew Duncan <andrewzboard@gmail.com> wrote:

More progress! This sounds good, but it looks like what you intend is for r to be the error message in the Result enum type.

enum Result {
case .Fail(String) // Error message
case .Succeed(MyType) // Something to work with
}

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
  return r // Looks like r is bound to the error String.
           // But maybe you meant r = the entire returnsResult() result.
}

I see. If it's an arbitrary pattern, you can match 'case let r' to bind the entire value instead of picking out the payload of the other case. That would still be exhaustive.

-Joe

The sort of message-passing error-handling I have in mind is where each method in the call chain returns a full Result enum and each stage checks it for Succeed/Fail, and immediately bails on Fail, returning (propagating) the Result. To be sure, this is sort of what exceptions do under the hood anyway.

My use-case is a recursive descent parser that I want to bail when a syntax error is found. This could happen way deep in the stack of calls. If I consistently return a .Fail(ErrorCode) or .Succeed(ASTNode) from each method, I just pass on the Result in case of .Fail, or use it in case of .Succeed.

On 23 Dec, 2015, at 15:35, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
  return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.

-Joe

_______________________________________________
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


(Félix Cloutier) #15

Wait, no, there's a problem with that. You can't use `r` in the guard scope because `returnsResult()` might not have succeeded.

Félix

···

Le 24 déc. 2015 à 09:37:36, Félix Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :

I like that it's consistent with the if syntax (even though I don't really like the if syntax) and that there's no dangling parts after the else.

Félix

Le 24 déc. 2015 à 06:29:17, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> a écrit :

What do you think of

guard let r = returnsResult(), case let .Succeed(m) = r else {
      return r
}

Which binds r only within the scope of the guard as desired.

Written in multiple lines

guard
      let r = returnsResult(),
      case let .Succeed(m) = r
else {
      return r
}

-Thorsten

Am 24.12.2015 um 01:02 schrieb Andrew Duncan via swift-evolution <swift-evolution@swift.org>:

Yes, which would revert to Brent’s suggestion. But you have generalized it in a very compatible way.

As I read somewhere, improving programming languages comes from removing limitations rather than adding features. I intend for this Pitch to be the former, although it does kind of look like the latter.

On 23 Dec, 2015, at 15:58, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 3:56 PM, Andrew Duncan <andrewzboard@gmail.com> wrote:

More progress! This sounds good, but it looks like what you intend is for r to be the error message in the Result enum type.

enum Result {
case .Fail(String) // Error message
case .Succeed(MyType) // Something to work with
}

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
return r // Looks like r is bound to the error String.
          // But maybe you meant r = the entire returnsResult() result.
}

I see. If it's an arbitrary pattern, you can match 'case let r' to bind the entire value instead of picking out the payload of the other case. That would still be exhaustive.

-Joe

The sort of message-passing error-handling I have in mind is where each method in the call chain returns a full Result enum and each stage checks it for Succeed/Fail, and immediately bails on Fail, returning (propagating) the Result. To be sure, this is sort of what exceptions do under the hood anyway.

My use-case is a recursive descent parser that I want to bail when a syntax error is found. This could happen way deep in the stack of calls. If I consistently return a .Fail(ErrorCode) or .Succeed(ASTNode) from each method, I just pass on the Result in case of .Fail, or use it in case of .Succeed.

On 23 Dec, 2015, at 15:35, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.

-Joe

_______________________________________________
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

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


(Thorsten Seitz) #16

The problem is that currently the if-let and guard-let syntax is reserved for unwrapping optionals and therefore cannot be used (at least not unambiguously) for simple let-bindings as well, which is required here.

My example therefore needs the following change (sorry, I did not make this explicit):

1. allow let-bindings in if- and guard-statements and
2. require explicit optional unwrapping by pattern matching in if- and guard-statements

In addition I’d like to add the following (but that’s really a separate proposal):
3. drop the „case“ keyword for pattern matching

The example would then look like follows (written in multiple lines for adding comments):

guard
     let r = returnsResult(), // simple let-binding
     let .Succeed(m) = r // pattern matching
else {
     return r
}

Unwrapping optionals would then look like follows (i.e. no special syntax for unwrapping optionals):

if let .Some(x) = x {
  …
}

A shorter alternative might be: if let x ?= x { … }

IIRC this or something similar was part of an earlier Swift release and was streamlined to the current syntax because optionals are quite common and already have special syntax sugar. The problem is that the current syntax while being convenient for its succinctness is ambiguous with simple let-bindings which is inconsistent and - more importantly - makes extending if-statements and guard-statements by simple let-bindings impossible.

-Thorsten

···

Am 24.12.2015 um 18:13 schrieb Félix Cloutier <felixcca@yahoo.ca>:

Wait, no, there's a problem with that. You can't use `r` in the guard scope because `returnsResult()` might not have succeeded.

Félix

Le 24 déc. 2015 à 09:37:36, Félix Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :

I like that it's consistent with the if syntax (even though I don't really like the if syntax) and that there's no dangling parts after the else.

Félix

Le 24 déc. 2015 à 06:29:17, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> a écrit :

What do you think of

guard let r = returnsResult(), case let .Succeed(m) = r else {
     return r
}

Which binds r only within the scope of the guard as desired.

Written in multiple lines

guard
     let r = returnsResult(),
     case let .Succeed(m) = r
else {
     return r
}

-Thorsten

Am 24.12.2015 um 01:02 schrieb Andrew Duncan via swift-evolution <swift-evolution@swift.org>:

Yes, which would revert to Brent’s suggestion. But you have generalized it in a very compatible way.

As I read somewhere, improving programming languages comes from removing limitations rather than adding features. I intend for this Pitch to be the former, although it does kind of look like the latter.

On 23 Dec, 2015, at 15:58, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 3:56 PM, Andrew Duncan <andrewzboard@gmail.com> wrote:

More progress! This sounds good, but it looks like what you intend is for r to be the error message in the Result enum type.

enum Result {
case .Fail(String) // Error message
case .Succeed(MyType) // Something to work with
}

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
return r // Looks like r is bound to the error String.
         // But maybe you meant r = the entire returnsResult() result.
}

I see. If it's an arbitrary pattern, you can match 'case let r' to bind the entire value instead of picking out the payload of the other case. That would still be exhaustive.

-Joe

The sort of message-passing error-handling I have in mind is where each method in the call chain returns a full Result enum and each stage checks it for Succeed/Fail, and immediately bails on Fail, returning (propagating) the Result. To be sure, this is sort of what exceptions do under the hood anyway.

My use-case is a recursive descent parser that I want to bail when a syntax error is found. This could happen way deep in the stack of calls. If I consistently return a .Fail(ErrorCode) or .Succeed(ASTNode) from each method, I just pass on the Result in case of .Fail, or use it in case of .Succeed.

On 23 Dec, 2015, at 15:35, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.

-Joe

_______________________________________________
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

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


(Lily Ballard) #17

You actually can do simple let-bindings in if-let and guard-let via the `case let` syntax:

guard case let r = returnsResult(), case let .Succeed(m) = r else { ... }

Although the problem with this is `r` still isn't visible inside of the else block, because it's part of the guard statement and none of the bound identifiers are visible inside of the else. But this really ends up being the equivalent of

let r = returnsResult()
guard case let .Succeed(m) = r else { return r }

anyway so I don't really see the point in pursuing this.

As for changing if-let and guard-let by default to do this style of matching, I believe the Swift team did consider doing that, but the overwhelming prevalence of Optionals in Swift is why they didn't go ahead with it. I bet if you examine your own code you'll find that normal if-let and guard-lets outweigh if-case and guard-case by a very large amount.

-Kevin

···

On Sun, Dec 27, 2015, at 08:25 AM, Thorsten Seitz via swift-evolution wrote:

The problem is that currently the if-let and guard-let syntax is reserved for unwrapping optionals and therefore cannot be used (at least not unambiguously) for simple let-bindings as well, which is required here.

My example therefore needs the following change (sorry, I did not make this explicit):

1. allow let-bindings in if- and guard-statements and
2. require explicit optional unwrapping by pattern matching in if- and guard-statements

In addition I’d like to add the following (but that’s really a separate proposal):
3. drop the „case“ keyword for pattern matching

The example would then look like follows (written in multiple lines for adding comments):

guard
     let r = returnsResult(), // simple let-binding
     let .Succeed(m) = r // pattern matching
else {
     return r
}

Unwrapping optionals would then look like follows (i.e. no special syntax for unwrapping optionals):

if let .Some(x) = x {
  …
}

A shorter alternative might be: if let x ?= x { … }

IIRC this or something similar was part of an earlier Swift release and was streamlined to the current syntax because optionals are quite common and already have special syntax sugar. The problem is that the current syntax while being convenient for its succinctness is ambiguous with simple let-bindings which is inconsistent and - more importantly - makes extending if-statements and guard-statements by simple let-bindings impossible.

-Thorsten

> Am 24.12.2015 um 18:13 schrieb Félix Cloutier <felixcca@yahoo.ca>:
>
> Wait, no, there's a problem with that. You can't use `r` in the guard scope because `returnsResult()` might not have succeeded.
>
> Félix
>
>> Le 24 déc. 2015 à 09:37:36, Félix Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :
>>
>> I like that it's consistent with the if syntax (even though I don't really like the if syntax) and that there's no dangling parts after the else.
>>
>> Félix
>>
>>> Le 24 déc. 2015 à 06:29:17, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> a écrit :
>>>
>>> What do you think of
>>>
>>> guard let r = returnsResult(), case let .Succeed(m) = r else {
>>> return r
>>> }
>>>
>>> Which binds r only within the scope of the guard as desired.
>>>
>>> Written in multiple lines
>>>
>>> guard
>>> let r = returnsResult(),
>>> case let .Succeed(m) = r
>>> else {
>>> return r
>>> }
>>>
>>> -Thorsten
>>>
>>>> Am 24.12.2015 um 01:02 schrieb Andrew Duncan via swift-evolution <swift-evolution@swift.org>:
>>>>
>>>> Yes, which would revert to Brent’s suggestion. But you have generalized it in a very compatible way.
>>>>
>>>> As I read somewhere, improving programming languages comes from removing limitations rather than adding features. I intend for this Pitch to be the former, although it does kind of look like the latter.
>>>>
>>>>> On 23 Dec, 2015, at 15:58, Joe Groff <jgroff@apple.com> wrote:
>>>>>
>>>>>
>>>>>> On Dec 23, 2015, at 3:56 PM, Andrew Duncan <andrewzboard@gmail.com> wrote:
>>>>>>
>>>>>> More progress! This sounds good, but it looks like what you intend is for r to be the error message in the Result enum type.
>>>>>>
>>>>>> enum Result {
>>>>>> case .Fail(String) // Error message
>>>>>> case .Succeed(MyType) // Something to work with
>>>>>> }
>>>>>>
>>>>>> guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
>>>>>> return r // Looks like r is bound to the error String.
>>>>>> // But maybe you meant r = the entire returnsResult() result.
>>>>>> }
>>>>>
>>>>> I see. If it's an arbitrary pattern, you can match 'case let r' to bind the entire value instead of picking out the payload of the other case. That would still be exhaustive.
>>>>>
>>>>> -Joe
>>>>>
>>>>>>
>>>>>> The sort of message-passing error-handling I have in mind is where each method in the call chain returns a full Result enum and each stage checks it for Succeed/Fail, and immediately bails on Fail, returning (propagating) the Result. To be sure, this is sort of what exceptions do under the hood anyway.
>>>>>>
>>>>>> My use-case is a recursive descent parser that I want to bail when a syntax error is found. This could happen way deep in the stack of calls. If I consistently return a .Fail(ErrorCode) or .Succeed(ASTNode) from each method, I just pass on the Result in case of .Fail, or use it in case of .Succeed.
>>>>>>
>>>>>>
>>>>>>> On 23 Dec, 2015, at 15:35, Joe Groff <jgroff@apple.com> wrote:
>>>>>>>
>>>>>>>
>>>>>>>> On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:
>>>>>>>
>>>>>>> A slight generalization would be to allow for an arbitrary pattern in the `else` clause:
>>>>>>>
>>>>>>> guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
>>>>>>> return r
>>>>>>> }
>>>>>>>
>>>>>>> with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.
>>>>>>>
>>>>>>> -Joe
>>>>
>>>> _______________________________________________
>>>> 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
>>
>> _______________________________________________
>> 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


(Thorsten Seitz) #18

You actually can do simple let-bindings in if-let and guard-let via the `case let` syntax:

guard case let r = returnsResult(), case let .Succeed(m) = r else { ... }

Although the problem with this is `r` still isn't visible inside of the else block, because it's part of the guard statement and none of the bound identifiers are visible inside of the else.

Which is correct because "case let" can fail as it is not a simple let-binding but pattn matching. That's why a real simple let-binding might be attractive to have. Names bound by these (or rather by let-bindings that happen before a failable binding like pattern matching or optionla unwrapping) should be available in the else block.

But this really ends up being the equivalent of

let r = returnsResult()
guard case let .Succeed(m) = r else { return r }

anyway so I don't really see the point in pursuing this.

The point was about keeping the scope of "r" restricted to the guard clause.
This might not be worth the effort, of course.

As for changing if-let and guard-let by default to do this style of matching, I believe the Swift team did consider doing that, but the overwhelming prevalence of Optionals in Swift is why they didn't go ahead with it. I bet if you examine your own code you'll find that normal if-let and guard-lets outweigh if-case and guard-case by a very large amount.

I agree without having to examine my code but I'm still unsure whether it wouldn't be better to unify let bindings with pattern matching while disambiguating simple bindings from pattern matching and optional unwrapping as special case of pattern matching:

let pattern = expression

If "pattern" is a simple variable the pattern matching cannot fail, so we have a simple let-binding. Using this in an if- or guard-statement would make the bound name available in the statement block(s) unless a failable pattern matching came before it.

For syntactically light optional unwrapping "expression" can be "x?", i.e. "if let x = x? {...}".
This syntax sugar would be in line with optional chaining, like "x?.property = 42" where we can understand "x?" to mean the unwrapped optional if present and otherwise skipping the statement.

-Thorsten

···

Am 28.12.2015 um 02:31 schrieb Kevin Ballard via swift-evolution <swift-evolution@swift.org>:

-Kevin

On Sun, Dec 27, 2015, at 08:25 AM, Thorsten Seitz via swift-evolution wrote:
The problem is that currently the if-let and guard-let syntax is reserved for unwrapping optionals and therefore cannot be used (at least not unambiguously) for simple let-bindings as well, which is required here.

My example therefore needs the following change (sorry, I did not make this explicit):

1. allow let-bindings in if- and guard-statements and
2. require explicit optional unwrapping by pattern matching in if- and guard-statements

In addition I’d like to add the following (but that’s really a separate proposal):
3. drop the „case“ keyword for pattern matching

The example would then look like follows (written in multiple lines for adding comments):

guard
    let r = returnsResult(), // simple let-binding
    let .Succeed(m) = r // pattern matching
else {
    return r
}

Unwrapping optionals would then look like follows (i.e. no special syntax for unwrapping optionals):

if let .Some(x) = x {
   …
}

A shorter alternative might be: if let x ?= x { … }

IIRC this or something similar was part of an earlier Swift release and was streamlined to the current syntax because optionals are quite common and already have special syntax sugar. The problem is that the current syntax while being convenient for its succinctness is ambiguous with simple let-bindings which is inconsistent and - more importantly - makes extending if-statements and guard-statements by simple let-bindings impossible.

-Thorsten

Am 24.12.2015 um 18:13 schrieb Félix Cloutier <felixcca@yahoo.ca>:

Wait, no, there's a problem with that. You can't use `r` in the guard scope because `returnsResult()` might not have succeeded.

Félix

Le 24 déc. 2015 à 09:37:36, Félix Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :

I like that it's consistent with the if syntax (even though I don't really like the if syntax) and that there's no dangling parts after the else.

Félix

Le 24 déc. 2015 à 06:29:17, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> a écrit :

What do you think of

guard let r = returnsResult(), case let .Succeed(m) = r else {
    return r
}

Which binds r only within the scope of the guard as desired.

Written in multiple lines

guard
    let r = returnsResult(),
    case let .Succeed(m) = r
else {
    return r
}

-Thorsten

Am 24.12.2015 um 01:02 schrieb Andrew Duncan via swift-evolution <swift-evolution@swift.org>:

Yes, which would revert to Brent’s suggestion. But you have generalized it in a very compatible way.

As I read somewhere, improving programming languages comes from removing limitations rather than adding features. I intend for this Pitch to be the former, although it does kind of look like the latter.

On 23 Dec, 2015, at 15:58, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 3:56 PM, Andrew Duncan <andrewzboard@gmail.com> wrote:

More progress! This sounds good, but it looks like what you intend is for r to be the error message in the Result enum type.

enum Result {
case .Fail(String) // Error message
case .Succeed(MyType) // Something to work with
}

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
return r // Looks like r is bound to the error String.
        // But maybe you meant r = the entire returnsResult() result.
}

I see. If it's an arbitrary pattern, you can match 'case let r' to bind the entire value instead of picking out the payload of the other case. That would still be exhaustive.

-Joe

The sort of message-passing error-handling I have in mind is where each method in the call chain returns a full Result enum and each stage checks it for Succeed/Fail, and immediately bails on Fail, returning (propagating) the Result. To be sure, this is sort of what exceptions do under the hood anyway.

My use-case is a recursive descent parser that I want to bail when a syntax error is found. This could happen way deep in the stack of calls. If I consistently return a .Fail(ErrorCode) or .Succeed(ASTNode) from each method, I just pass on the Result in case of .Fail, or use it in case of .Succeed.

On 23 Dec, 2015, at 15:35, Joe Groff <jgroff@apple.com> wrote:

On Dec 23, 2015, at 10:16 AM, Andrew Duncan via swift-evolution <swift-evolution@swift.org> wrote:

A slight generalization would be to allow for an arbitrary pattern in the `else` clause:

guard case let .Succeed(m) = returnsResult() else case let .Failure(r) {
return r
}

with the requirement that the "guard" and "else" patterns form an exhaustive match when taken together. That feels nicer than special-case knowledge of two-case enums, though I admit it punishes what's likely to be a common case.

-Joe

_______________________________________________
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

_______________________________________________
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

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