aboutsummaryrefslogtreecommitdiff
path: root/docs/notes/goto.txt
blob: ee134156d320a0a77bcdcf358c54ab9096f984a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

goto
----
Some people are very certain that goto (and similar statements such as
continue) should be avoided, because it's "unstructured". I think that
there are ways of using goto that are actually more structured (or at
least easier to read) than the equivalent sequences of "if" statements
and boolean flags.

From my experience of programming, they way I've used goto (or felt the
need for using it) has been to skip to the end of a block, or break out
of loops. I don't think I've ever used goto to jump back in program flow
for instance.

Since goto is not generally accepted, I will describe some alternative
techniques, that are equivalent to the "structured" uses of goto.


loop/else
---------
Python programmers can use the following convenient syntax:

    for e in elements:
        if e == searched_for: break
    else:
        print "not found"

This way you avoid the boolean "found" flag you'd otherwise have to use:

    bool found = false;
    for (Element e : elements) {
        if (e == searched_for) {
            found = true;
            break;
        }
    }
    if (!found) {
        printf("not found");
    }

This could be implemented in LRL as well:

    for (Element e in elements) {
        if (e == searched_for) break;
    } else {
        print("not found");
    }

The compiler should check that the else is unambigious, though. For
instance, this code should not compile:

    for (Element e in elements)
        if (e == searched_for) break;
    else // does it belong to if or for?
        print("not found");


In general, I think that {} should always be used for indented statements.


loop/if and if/if
-----------------

Consider this example code in C, and say "another" and "whatever" are quite
long expressions, so you don't want to combine them into one if statement:

    while (something) {
        if (another) continue;
        if (!whatever) continue;
        
        // do some work
        work();
    }

This can be rewritten as (even in existing languages, like C, but I've
never seen it used):

    while (something)
    if (!another)
    if (whatever) {
        // do some work
        work();
    }

The advantage over traditional "if" blocks is that you get visual structure
(the ifs are vertically aligned) and that it requires less blocks - which
means less indentation levels and less } to search for when you read the
code.

There is a problem with this syntax though, if you combine in the loop-else:

    while (something)
    if (whatever) {
        // ...
    } else { // does it bind to if or while?
        // ...
    }

I think such code should not compile. Here's an alternative syntax, which is
clearer:

    while (something) {
        if (!another)
        if (whatever) {
            // ...
        }
    } else {
        // ...
    }

But still, unlike if/continue, you can't put comments above individual "if"
statements without hurting readability. So I'm not sure if this is an
improvement over if/continue.


goto cleanup/error/end
----------------------
This is a common code pattern in C programs. Often, it's related to some
form of memory or resource management, but it's sometimes used for error
handling or breaking out of a execution flow in general. It's the
equivalent of try/catch/finally in languages with exceptions (altough
goto is more efficient, at least without optimizations).

    try {
        
        if (!need_to_work()) skip;
        
        if (!try_to_work()) fail;
        try_to_work() or fail; // equivalent
        
        return work;
        
    } catch {
        print("error");
        return NULL;
        
    } finally {
        remove_task();
    }


The "return" statements can be translated into this pseudo assembler code:

    // return work;
    mov  return-register, work
    jmp  finally
    
  finally:
    mov  [stack], return-register
    call find_next_task
    mov  return-register, [stack]
    ret


try-then
--------

    try {
        Stuff^ s = getStuff()?;
        Thing^ t = s->getThing()?;
        String name = t->getName()?;
        int i = t->getNumber();
        then {
            io:writeln("name: ", name, " number: ", i);
        }
    }


goto skip
---------

This is maybe not so common, but I think a structured version would make
sense. Goto version:

    /* Read statement, or skip to next ; on error */
    if (!read_name()) goto skip;
    if (!expected('=')) goto skip;
    if (!read_value()) goto skip;
    
  skip:
    skip_past_semicolon();


Structured version:

    flow {
        if (!read_name()) leave;
        if (!expected('=')) leave;
        if (!read_value()) leave;
    }
    skip_past_semicolon();


"specific goto"
---------------
It can be hard to see what a "goto x" does in a program. A possible solution
to this problem is introduce new keywords:

    breakto      -- only for breaking out of multiple loops
    continueat   -- only for continuing at the next iteration in multiple loops
    skipto       -- only for going forward in a function
    repeatfrom   -- only for going backward in a function

E.g.

  outer:
    for x in xlist {
        for y in ylist {
            if x==y continueat x
            if not is_valid(x) skipto end;
            // ...
        }
    }
    
    do_stuff();
  end:
    cleanup();