[Idea] Add forced conversion for Error catching pattern matching


(Tyler Cloutier) #1

I recall that there was quite a bit of discussion a while back about adding typed error declarations for methods that throw for the purpose of exhaustive pattern matching on errors.

There were interesting arguments on either side, and I think that the result was to maintain the status quo. There’s still the issue of having to add the extra catch statement to every do block for exhaustive matches.

Would it be wise to allow force conversion for the cases in which the developer believes the match is exhaustive? ie

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as ActionError {
    game.failedAction = e
} catch _ {
    fatalError(“This is an unfortunate bit of noise :/")
}

becomes

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as! ActionError {
    game.failedAction = e
}

Also as a brief aside, it’s not super intuitive to me that the syntax for the catch pattern matching wildcard is

catch _

whereas it is

default

for switches. I think I saw Chris mention somewhere that default was chosen because of it’s wide familiarity. Does anyone recall the reason?

Thanks,

Tyler


(Pedro Vieira) #2

Also as a brief aside, it’s not super intuitive to me that the syntax for
the catch pattern matching wildcard is

catch _

whereas it is

default

for switches. I think I saw Chris mention somewhere that default was
chosen because of it’s wide familiarity. Does anyone recall the reason?

I think it was about a proposal for using case _: instead of default:

···

2016-03-20 20:26 GMT+00:00 Tyler Fleming Cloutier via swift-evolution < swift-evolution@swift.org>:

I recall that there was quite a bit of discussion a while back about
adding typed error declarations for methods that throw for the purpose of
exhaustive pattern matching on errors.

There were interesting arguments on either side, and I think that the
result was to maintain the status quo. There’s still the issue of having to
add the extra catch statement to every do block for exhaustive matches.

Would it be wise to allow force conversion for the cases in which the
developer believes the match is exhaustive? ie

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as ActionError {
    game.failedAction = e
} catch _ {
    fatalError(“This is an unfortunate bit of noise :/")
}

becomes

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as! ActionError {
    game.failedAction = e
}

Also as a brief aside, it’s not super intuitive to me that the syntax for
the catch pattern matching wildcard is

catch _

whereas it is

default

for switches. I think I saw Chris mention somewhere that default was
chosen because of it’s wide familiarity. Does anyone recall the reason?

Thanks,

Tyler

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

--
Pedro Vieira
http://pedrovieira.me


(Greg Parker) #3

Yes, both `switch` and `default` were chosen because that's what other C-style languages use.

Note that `case _` is the same as `default` in `switch`, so you can use `case _` and `catch _` if you don't care about C-style appearance.

I don't think anyone has proposed allowing `default` in place of `catch _`. There was an earlier discussion of removing `default` from `switch` and requiring `case _` instead (thread "Remove default case in switch-case" on swift-evolution).

···

On Mar 20, 2016, at 1:26 PM, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Also as a brief aside, it’s not super intuitive to me that the syntax for the catch pattern matching wildcard is

catch _

whereas it is

default

for switches. I think I saw Chris mention somewhere that default was chosen because of it’s wide familiarity. Does anyone recall the reason?

--
Greg Parker gparker@apple.com <mailto:gparker@apple.com> Runtime Wrangler


(Haravikk) #4

I’m a ±1; for the way things are I’m a +1, but I think I’d still prefer to have typed errors that the compiler can use to check for an exhaustive list, as it would be easy for a forced conversion to result in unexpected runtime errors later if you ever need to add a new error type to your .applyAction() method, since doing so wouldn’t result in any warnings/errors at compile time.

I agree about catch _ and default; either default should be allowed for do/catch, or be removed from switches, to promote consistency, but that’s a separate issue I think.

···

On 20 Mar 2016, at 20:26, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

I recall that there was quite a bit of discussion a while back about adding typed error declarations for methods that throw for the purpose of exhaustive pattern matching on errors.

There were interesting arguments on either side, and I think that the result was to maintain the status quo. There’s still the issue of having to add the extra catch statement to every do block for exhaustive matches.

Would it be wise to allow force conversion for the cases in which the developer believes the match is exhaustive? ie

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as ActionError {
    game.failedAction = e
} catch _ {
    fatalError(“This is an unfortunate bit of noise :/")
}

becomes

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as! ActionError {
    game.failedAction = e
}

Also as a brief aside, it’s not super intuitive to me that the syntax for the catch pattern matching wildcard is

catch _

whereas it is

default

for switches. I think I saw Chris mention somewhere that default was chosen because of it’s wide familiarity. Does anyone recall the reason?

Thanks,

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


(Radek Pietruszewski) #5

Also as a brief aside, it’s not super intuitive to me that the syntax for the catch pattern matching wildcard is

catch _

whereas it is

default

for switches.

I do believe you can just say `catch`:

  4> do {
  5. try throwing()
  6. } catch let e as E { ...
  7. } catch {
  8. print(error)
  9. }

— Radek

···

On 20 Mar 2016, at 21:26, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

I recall that there was quite a bit of discussion a while back about adding typed error declarations for methods that throw for the purpose of exhaustive pattern matching on errors.

There were interesting arguments on either side, and I think that the result was to maintain the status quo. There’s still the issue of having to add the extra catch statement to every do block for exhaustive matches.

Would it be wise to allow force conversion for the cases in which the developer believes the match is exhaustive? ie

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as ActionError {
    game.failedAction = e
} catch _ {
    fatalError(“This is an unfortunate bit of noise :/")
}

becomes

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as! ActionError {
    game.failedAction = e
}

Also as a brief aside, it’s not super intuitive to me that the syntax for the catch pattern matching wildcard is

catch _

whereas it is

default

for switches. I think I saw Chris mention somewhere that default was chosen because of it’s wide familiarity. Does anyone recall the reason?

Thanks,

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


(Brent Royal-Gordon) #6

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as ActionError {
    game.failedAction = e
} catch _ {
    fatalError(“This is an unfortunate bit of noise :/")
}

If you're just worried about the noise...

  try! {
    do {
      let action = chooseAction(game)
      game = try game.applyAction(action)
    }
    catch let e as ActionError {
      game.failedAction = e
    }
  }()

Or if that's too much syntax:

  func mustSucceed<Return>(function: () throws -> Return) -> Return {
    return try! function()
  }
  
  mustSucceed {
    do {
      let action = chooseAction(game)
      game = try game.applyAction(action)
    }
    catch let e as ActionError {
      game.failedAction = e
    }
  }

Or, if you use this specific ActionError logic widely, you can put that in a function:

  func catchActionError<Return>(function: () throws -> Return) -> Return? {
    do {
      return try function()
    }
    catch e as ActionError {
      game.failedAction = e
      return nil
    }
    catch {
      fatalError()
    }
  }
  
  catchActionError {
    let action = chooseAction(game)
    game = try game.applyAction(action)
  }

···

--
Brent Royal-Gordon
Architechies


(Michel Fortin) #7

If you move the cast on the next line it'll compile and do what you want with exactly the same amount of code:

  do {
      let action = chooseAction(game)
      game = try game.applyAction(action)
  } catch let e {
      game.failedAction = e as! ActionError
  }

···

Le 20 mars 2016 à 16:26, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :

Would it be wise to allow force conversion for the cases in which the developer believes the match is exhaustive? ie

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as ActionError {
    game.failedAction = e
} catch _ {
    fatalError(“This is an unfortunate bit of noise :/")
}

becomes

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as! ActionError {
    game.failedAction = e
}

--
Michel Fortin
https://michelf.ca


(Tyler Cloutier) #8

I’m a ±1; for the way things are I’m a +1, but I think I’d still prefer to have typed errors that the compiler can use to check for an exhaustive list, as it would be easy for a forced conversion to result in unexpected runtime errors later if you ever need to add a new error type to your .applyAction() method, since doing so wouldn’t result in any warnings/errors at compile time.

That is true, but I think that is the intended behavior. Consider the case where applyAction is a method which is to be overridden in by a subclass. If the subclass needs to throw a different type of error, it cannot because the it’s not declared super classes method definition. I ran into this problem the other day in Java. The solution was to throw a runtime exception and in Java it seems that there is a tendency to drift towards more and more runtime exceptions for exactly these types of reasons. The problem with runtime exceptions is that there’s no way to know if they are thrown by a method at it’s call site.

At least with the as! operator, people familiar with Swift will immediately recognize that as a potentially app crashing operation.

···

On Mar 21, 2016, at 6:01 AM, Haravikk <swift-evolution@haravikk.me> wrote:

I agree about catch _ and default; either default should be allowed for do/catch, or be removed from switches, to promote consistency, but that’s a separate issue I think.

On 20 Mar 2016, at 20:26, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I recall that there was quite a bit of discussion a while back about adding typed error declarations for methods that throw for the purpose of exhaustive pattern matching on errors.

There were interesting arguments on either side, and I think that the result was to maintain the status quo. There’s still the issue of having to add the extra catch statement to every do block for exhaustive matches.

Would it be wise to allow force conversion for the cases in which the developer believes the match is exhaustive? ie

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as ActionError {
    game.failedAction = e
} catch _ {
    fatalError(“This is an unfortunate bit of noise :/")
}

becomes

do {
    let action = chooseAction(game)
    game = try game.applyAction(action)
} catch let e as! ActionError {
    game.failedAction = e
}

Also as a brief aside, it’s not super intuitive to me that the syntax for the catch pattern matching wildcard is

catch _

whereas it is

default

for switches. I think I saw Chris mention somewhere that default was chosen because of it’s wide familiarity. Does anyone recall the reason?

Thanks,

Tyler
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tyler Cloutier) #9

These I think add noise in terms of someone reading the code. Now not only do they need to know Swift’s error handling mechanism, but also they have to be familiar with my custom wrapper around it.

···

On Mar 21, 2016, at 8:26 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

do {
   let action = chooseAction(game)
   game = try game.applyAction(action)
} catch let e as ActionError {
   game.failedAction = e
} catch _ {
   fatalError(“This is an unfortunate bit of noise :/")
}

If you're just worried about the noise...

  try! {
    do {
      let action = chooseAction(game)
      game = try game.applyAction(action)
    }
    catch let e as ActionError {
      game.failedAction = e
    }
  }()

Or if that's too much syntax:

  func mustSucceed<Return>(function: () throws -> Return) -> Return {
    return try! function()
  }
  
  mustSucceed {
    do {
      let action = chooseAction(game)
      game = try game.applyAction(action)
    }
    catch let e as ActionError {
      game.failedAction = e
    }
  }

Or, if you use this specific ActionError logic widely, you can put that in a function:

  func catchActionError<Return>(function: () throws -> Return) -> Return? {
    do {
      return try function()
    }
    catch e as ActionError {
      game.failedAction = e
      return nil
    }
    catch {
      fatalError()
    }
  }
  
  catchActionError {
    let action = chooseAction(game)
    game = try game.applyAction(action)
  }

--
Brent Royal-Gordon
Architechies


(Tyler Cloutier) #10

This is actually quite a good solution. It wouldn’t be quite as consistent if I had multiple catch statements for different types before this but it really gets almost all of the way there. Thanks!

···

On Mar 21, 2016, at 6:20 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 20 mars 2016 à 16:26, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :

Would it be wise to allow force conversion for the cases in which the developer believes the match is exhaustive? ie

do {
   let action = chooseAction(game)
   game = try game.applyAction(action)
} catch let e as ActionError {
   game.failedAction = e
} catch _ {
   fatalError(“This is an unfortunate bit of noise :/")
}

becomes

do {
   let action = chooseAction(game)
   game = try game.applyAction(action)
} catch let e as! ActionError {
   game.failedAction = e
}

If you move the cast on the next line it'll compile and do what you want with exactly the same amount of code:

  do {
      let action = chooseAction(game)
      game = try game.applyAction(action)
  } catch let e {
      game.failedAction = e as! ActionError
  }

--
Michel Fortin
https://michelf.ca


(Thorsten Seitz) #11

These I think add noise in terms of someone reading the code. Now not only do they need to know Swift’s error handling mechanism, but also they have to be familiar with my custom wrapper around it.

That is the case with all abstractions so I don't see why that should be a problem here, especially as the last example given by Brent was very succinct and expressive.

-Thorsten

···

Am 22.03.2016 um 07:19 schrieb Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org>:

On Mar 21, 2016, at 8:26 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

do {
  let action = chooseAction(game)
  game = try game.applyAction(action)
} catch let e as ActionError {
  game.failedAction = e
} catch _ {
  fatalError(“This is an unfortunate bit of noise :/")
}

If you're just worried about the noise...

   try! {
       do {
           let action = chooseAction(game)
           game = try game.applyAction(action)
       }
       catch let e as ActionError {
           game.failedAction = e
       }
   }()

Or if that's too much syntax:

   func mustSucceed<Return>(function: () throws -> Return) -> Return {
       return try! function()
   }
   
   mustSucceed {
       do {
           let action = chooseAction(game)
           game = try game.applyAction(action)
       }
       catch let e as ActionError {
           game.failedAction = e
       }
   }

Or, if you use this specific ActionError logic widely, you can put that in a function:

   func catchActionError<Return>(function: () throws -> Return) -> Return? {
       do {
           return try function()
       }
       catch e as ActionError {
           game.failedAction = e
           return nil
       }
       catch {
           fatalError()
       }
   }
   
   catchActionError {
       let action = chooseAction(game)
       game = try game.applyAction(action)
   }

--
Brent Royal-Gordon
Architechies

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