From: "lloeki (Loic Nageleisen) via ruby-core" <ruby-core@...> Date: 2024-01-08T14:21:48+00:00 Subject: [ruby-core:116067] [Ruby master Feature#20160] rescue keyword for case expressions Issue #20160 has been updated by lloeki (Loic Nageleisen). Description updated Some clarifications. ---------------------------------------- Feature #20160: rescue keyword for case expressions https://bugs.ruby-lang.org/issues/20160#change-106064 * Author: lloeki (Loic Nageleisen) * Status: Open * Priority: Normal ---------------------------------------- It is frequent to find this piece of hypothetical Ruby code: ``` case (parsed = parse(input)) when Integer then handle_int(parsed) when Float then handle_float(parsed) end ``` What if we need to handle `parse` raising a hypothetical `ParseError`? Currently this can be done in two ways. Either option A, wrapping `case .. end`: ``` begin case (parsed = parse(input)) when Integer then handle_int(parsed) when Float then handle_float(parsed) # ... end rescue ParseError # ... end ``` Or option B, guarding before `case`: ``` begin parsed = parse(input) rescue ParseError # ... end case parsed when Integer then handle_int(parsed) when Float then handle_float(parsed) # ... end ``` The difference between option A and option B is that: - option A `rescue` is not localised to parsing and also covers code following `when` (including calling `===`), `then`, and `else`, which may or may not be what one wants. - option B `rescue` is localised to parsing but moves the definition of the variable (`parsed`) and the call to what is actually done (`parse(input)`) far away from `case`. With option B in some cases the variable needs to be introduced even though it might not be needed in `then` parts (e.g if the call in `case` is side-effectful or its value simply leading to branching decision logic). The difference becomes important when rescued exceptions are more general (e.g `Errno` stuff, `ArgumentError`, etc..), as well as when we consider `ensure` and `else`. I feel like option B is the most sensible one in general, but it adds a lot of noise and splits the logic in two parts. I would like to suggest a new syntax: ``` case (parsed = parse(input)) when Integer then handle_int(parsed) when Float then handle_float(parsed) rescue ParseError # ... rescue ArgumentError # ... else # ... fallthrough for all rescue and when cases ensure # ... called always end ``` If more readability is needed as to what these `rescue` are aimed to handle - being more explicit that this is option B - one could optionally write like this: ``` case (parsed = parse(input)) rescue ParseError # ... rescue ArgumentError # ... when Integer then handle_int(parsed) when Float then handle_float(parsed) ... else # ... ensure # ... end ``` Examples: - A made-up pubsub streaming parser with internal state, abstracting away reading from source: ``` parser = Parser.new(io) loop do case parser.parse # blocks for reading io in chunks rescue StandardError => e if parser.can_recover?(e) # tolerate failure, ignore next else emit_fail(e) end when :integer emit_integer(parser.last) when :float emit_float(parser.last) when :done # e.g EOF reached, IO closed, YAML --- end of doc, XML top-level closed, whatever makes sense emit_done break else parser.rollback # e.g rewinds io, we may not have enough data ensure parser.checkpoint # e.g saves io position for rollback end end ``` - Network handling, extrapolated from [ruby docs](https://ruby-doc.org/stdlib-2.7.1/libdoc/net/http/rdoc/Net/HTTP.html#class-Net::HTTP-label-Following+Redirection): ``` case (response = Net::HTTP.get_response(URI(uri_str)) rescue URI::InvalidURIError # handle URI errors rescue SocketError # handle socket errors rescue # other general errors when Net::HTTPSuccess response when Net::HTTPRedirection then location = response['location'] warn "redirected to #{location}" fetch(location, limit - 1) else response.value ensure @counter += 1 end ``` Credit: the idea initially came to me from [this article](https://inside.java/2023/12/15/switch-case-effect/), and thinking how it could apply to Ruby. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/