From 1c0c3eaee9bce887dfea60aeed7641ef3755c6cf Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Fri, 3 Mar 2023 09:02:57 +0100 Subject: [PATCH] WIP: PyGOTO --- content/python-goto.md | 217 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 content/python-goto.md diff --git a/content/python-goto.md b/content/python-goto.md new file mode 100644 index 0000000..3ad7a88 --- /dev/null +++ b/content/python-goto.md @@ -0,0 +1,217 @@ + +Goto in Python +============== + +I really know that Python was meant to be used without a goto, but sometimes it +really makes sense to use it. + +## Goto usage patterns: + +Let's first show a few common and in my opinion sensible (at least sometimes) +uses of goto. Also, I'll discuss possible goto-free alternatives of the code +and why they feel worse. + +### Error handling + +The most common one, used all over the Linux kernel for example: + +```c +// ... + +if(! something_we_dont_want_to_fail()): + goto err; +// We know it hasn't failed, so we can use it + +// ... + +return result; // OK + +err: +clean_up() +return 0; // This did not work. +``` + +Often, there are more goto's for one label, and fall through is utilized, e.g.: + +```c +// Open the file +if (file_empty(f)) + goto clean_files; +// Read the file +if (bad_data(f)) + goto clean_files; +// Load data into memory +if (could_not_process(data)) + goto clean_memory; +return processed_data; + +clean_memory: +free(data); +clean_files: +close(f); + +return 0; + +``` + +The goto-free alternative is to put everything into functions and check all the +functions. This might lead to creating functions with no semantics, and that +does not generally make the code more readable. + +The lucky part is, this is the case that is simple to solve with exceptions in +much the same way. + +There is still a case to be made with the multiple cleanup blocks and +fall-through, but one of the ways of solving that is to have the exception know +what to clean up. That might be a bit ugly, but the goto is way worse. + + +### Alternative methods of evaluation + +Sometimes, one would like to try several different paths to get a result, in +some order, i.e. with pseudocode like this: + +```c +int result = 0; + +// Method 1 (the most likely to work) +result = method_1(); +if(result) + goto done; + +// Method 1 did not work, try method 2: +result = method_2(); +if(result) + goto done; + +// Try method 3 maybe? +result = method_3(); +if (! result) + return ERROR; + +done: +// We now have result, so process that +// ... +``` + +Possible options to fixing this: + +- Create a function that gets the result. In that function, the goto's can be + replaced by return's, so that is nice, but the function might not have + sensible name and need an unreasonable number of arguments, if the methods + are complicated enough. +- Reorder the methods to try from the least preffered to most. That obscures + the algorithm, and might not be an option, if the methods have side effects. + +There exists a solution with exceptions: Raise an exception when one method +fails, and try other methods in the exception handling block. That leads to +complicated catch[^I will call it "catch" block even though in Python this +technically is an "except" block, because it seems more obvious what I mean.] +blocks, which is not a good practice. And what is worse, for the third method +the blocks would be nested! + +### Retries + +Sometimes, you need to try something several times, or skip something. In some +code (not originally in C), I had something along the lines of: + +```c +struct line_with_number get_next_reasonable_line(): + // Imagine here a static FILE object, from which I read the lines. + int linelen = 255; + char *line; + static int line_number + +next_line: + getline(line, linelen, file); + if (line == NULL) + // Just return some kind of error + return {.line = NULL, .number = line_number}; + + line = strip_comments(line); + if (strlen(line) == 0) + goto next_line; + // Look at the line more closely + if (we_do_not_like(line)) + goto next_line; + + return {.line = line, .number = line_number}; +``` + +The "obvious" solution to this is to use `while` and `continue`. But that is +not necessarily better -- the code is quite likely to pass only once, so there +is no semantics of a cycle. Also, what would be the condition here? We have no +data, so maybe `while(true)`. Or in the better case, `do ... while(strlen(line) +== 0 || we_do_not_like(line))`. When the condition is not so simple, using +infinite loop is the way to go. But such code is not prettier, since it uses +unreasonable flow control and loses the jump's meaning. + + +There may be a few more places where a goto is a sensible option, but I don't +recall any of them, so they might not be as important. + +## What to do in Python? + +There is no simple way around the second use-case for a goto. In some code, I +ended up with following code (and to make it worse, that was inside a function, +all of it): + +```python +# NOTE: This is really ugly goto, simulated using exceptions +class EverythingIsOK(Exception): pass + +try: + # Method 1: + result = method_1() + if result: raise EverythingIsOK() # Goto + + # Method 1 did not work, try method 2: + result = method_2() + if result: raise EverythingIsOK() + + # Try method 3 maybe? + result = method_3() + if not result: + raise OperationalError() # Not OK, Not goto. +except EverythingIsOK: # Label + pass + +# Process result +``` + +This is ugly enough, but can only jump downwards. Jumping upwards probably +needs the trick with `continue`, and unfortunately, there is no `do-while` in +Python. + +```python +while True: # Label + # code + continue # Goto + # code + break # Boilerplate +``` + +If you do not like that the goto's look different, you can use this mess: + +```python +class MyGoto(Exception): pass +while True: # Label + try: + # code + raise MyGoto() # Goto + # code + break + except MyGoto: # Even more boilerplate. + continue +``` + +I have no solution for using a single label for jumping from above _and_ below. +But I hope I will never see code that needs that. + +This post is not meant to encourage use of goto. It is considered harmful, and +in most cases, rightfully so. But still, there are some reasonable use cases, +so it is worth knowing when to use it and what can you do when the language +does not support goto. + +And probably needless to say: I would still rather have native goto support, +than need to use awful hacks to simulate the flow which I have in mind.