Go "switch": cat amongst the pigeons

450 views
Skip to first unread message

lucio

unread,
Mar 30, 2012, 12:22:11 PM3/30/12
to golang-nuts, lucio...@gmail.com
Now that Go 1 has been officially released, I think it is safe to make
a rather radical suggestion regarding the Go switch statement. I am
afraid that I may not do the subject justice, but I have to give it a
try.

The C switch statement on which Go's is modelled required a "break"
statement at the end of each case section to prevent flowing into the
following section. In Go, the default is to exit the switch at the
end of a case section, with a fall through option. This is perfectly
reasonable logic: I have not yet used the fall through option and I
can't even be sure I remember its name. Whereas in C the use of break
is pandemic. If that is not evidence that Go beats C in this
situation, nothing is.

But...

The reality is that in either model, we are constrained by the linear
nature of the representation of computer programs to one single
possibility: you either drop into the next section or you don't.
Which means that if you have a single situation that can be
represented in this fashion, you win, but the moment, say, two
distinct case sections should both continue into the same third case
section, there is no mechanism (short of a GO TO? I don't even want
to contemplate that option) that makes this possible. It is even more
serious when there are a few such situations within the same switch.

Now, what problem am I trying to solve here? It would seem reasonable,
at the end of each case section, to be able to determine not just that
we can continue with the next section in linear sequence, but rather
to select, possibly dynamically, which section should be selected,
with the default, as is the case in Go, to drop right out.

This reminds me of the options explored by E.J. Dijkstra in "A
Discipline of Programming" where the if statement resembles the Go
switch pretty closely, with, in his case, boolean guards controlling
access to each section and a single invocation of one section,
arbitrarily selected from one or more eligible ones. In Dijkstra's
design there was no consideration whatsoever of exiting one section
and entering another, the design description of the if statement
discouraged thinking in those terms.

The do statement, on the other hand, seemed more interesting in this
respect. The syntax was identical to that of the if statement, except
the keyword "do" enforced repeated execution until none of the guards
applied (in the if statement, failure of any guard to execute cause
program failure, no longer a fashionable approach - a do where no
guard applies is just an empty statement in Dijkstra's design). Here
it is trivial, if perhaps not very elegant, to arrange for the guard
conditions to be such that any section that wants to be followed by
another may be able to set the necessary condition. Sections that want
to exit the switch would have to force the necessary condition for
that.

And if a language designer is more savvy than I, maybe he or she can
develop an approach to this that I have not yet discovered and that
removes the lack of elegance mentioned above.

In summary, I believe that Go could benefit from some study of the
principles in "A Discipline of Programming", difficult as that book
was to grasp in many respects. I no longer have a copy and can't bring
myself to buy one. I do believe that Go addresses many of the issues
in the book better than Dijkstra did, what, thirty years ago? But in
the one respect, I think Dijkstra still has the edge.

I don't mind being called heretical, I am really interested in what
other people here feel about the options. I have intentionally not
mentioned Go's select, because I have not examined it in detail and
I'm not sure how it fits in the discussion. I appreciate that I may
seem to be talking out of turn, but this would not be the first time.

Lastly, I don't know how well I can defend my stance, but I'm willing
to try. I'm not very strong on language theory.

Lucio.

Jan Mercl

unread,
Mar 30, 2012, 1:07:47 PM3/30/12
to golan...@googlegroups.com, lucio...@gmail.com
On Friday, March 30, 2012 6:22:11 PM UTC+2, lucio wrote:
Now that Go 1 has been officially released, I think it is safe to make
a rather radical suggestion regarding the Go switch statement.

Just to outline the framework within which this discussion can be IMO considered rational...

Now that Go 1 has been officially released with the official promise to not break any Go 1 program in the future, I think it's pointless to suggest any changes to the semantics of the Go switch statement as long as such changes would break the aforementioned promise. Which leaves not so much space for "a rather radical suggestion regarding the Go switch statement", I'm afraid.

Or is this the start of the Go 2 design process? ;-)

si guy

unread,
Mar 30, 2012, 1:42:30 PM3/30/12
to golan...@googlegroups.com, lucio...@gmail.com
On Friday, March 30, 2012 9:22:11 AM UTC-7, lucio wrote:
{snip}

Now, what problem am I trying to solve here? It would seem reasonable,
at the end of each case section, to be able to determine not just that
we can continue with the next section in linear sequence, but rather
to select, possibly dynamically, which section should be selected,
with the default, as is the case in Go, to drop right out.
{snip}

This can  be done already via a re-assignment to the switch variable and a "goto" jump to just before the switch.
And if you are looking for arbitrary selection of cases, select does that on channel ops.

A requirement for this kind of logical flow has lead me to build state machines instead.
And the ability to pass function types in go (thanks again Rob for showing me the lexer) as returns makes this quite a bit easier.


Kyle Lemons

unread,
Mar 30, 2012, 1:42:18 PM3/30/12
to Jan Mercl, golan...@googlegroups.com, lucio...@gmail.com
If I read his suggestion correctly, it would be an extension to switch to support nonlinear fallthrough or the addition of a "do" pseudo-switch construct, so it would (or could) be code compatible. 

Lucio De Re

unread,
Mar 30, 2012, 2:15:24 PM3/30/12
to si guy, golan...@googlegroups.com
Yes, Rob's lexer is extremely elegant, maybe what I'm looking for lies
in formalising that idiom. I do find it interesting that a long time
ago there was already a suggestion somewhat outside the box, it is
nice that Go is equally innovative, if in a different way and with
different motivations.

And, yes, I think Go 2 could look at consolidating for, if, switch and
select into a single paradigm, building both on Dijkstra's work and
Go's extremely clever and pragmatic initialisations. I certainly don't
expect Go 1 to change so dramatically.

Lucio.

Steven Blenkinsop

unread,
Mar 30, 2012, 2:46:44 PM3/30/12
to lucio, golang-nuts, lucio...@gmail.com

On Mar 30, 2012, at 12:22 PM, lucio <lucio...@gmail.com> wrote:

> (short of a GO TO? I don't even want to contemplate that option)

And yet all you're doing is designing a more complex way of doing a goto ;). Goto isn't actually as bad as people seem to think. Like any feature, it can and has been abused, but there are also correct uses.

You used to be able to put labels in a switch and jump between them with goto. However, that was removed when jumping into nested or parallel blocks was outlawed (rightfully so, imo, since it often indicates a bad code structure). You can only jump within a block or into containing blocks now. I personally think jumping between cases within a switch block is one of the better and easier to reason about uses of goto though, since the cases are by default mutually exclusive.

Anyways, you can restart a switch by labeling it and jumping to the label. The idea has occurred to me that perhaps a continue statement for switches would be useful so you can keep checking subsequent conditions without having to go through all the conditions you've already checked and invalidating them somehow. It would also make continue symmetric with break since right now break operates on the switch while continue operates on a containing loop, but that would be a breaking change so it's not going to happen.

Michael Jones

unread,
Mar 31, 2012, 5:45:29 AM3/31/12
to Steven Blenkinsop, lucio, golang-nuts
Lucio,

Apparently you want the "all" form of guarded execution, that is, consider units of code each preceded by a conditional test and execute the code in every unit that passes the test, as opposed to the "first" form of guarded execution expressed by the switch statement. Is this so?

One way to think of Go's implicit "break" at the end of each code unit is that (broadly) a switch:

switch {
test_a: code_a
test_b: code_b
}

is really:

if test_a {
    code_a
} else if test_b {
    code_b
}

and you are proposing that there be a mechanism for:

if test_a {
    code_a
}
if test_b {
    code_b
}

...such that every unit whose guard condition is true is executed. [step one] but further with the "as much as possible" aspect [step 2] which changes this to:

for again := true; again == true {
    again = false
    if test_a {
        again = true
        code_a
    }
    if test_b {
        again = true
        code_b
    }
}

If I've understood correctly then what you want is not literally an extension of the semantics of switch (since that specifically implies the "first" / "else" version) but rather a new keyword, a sister to switch that does NOT switch and which also iterates. You'll need to propose a name for this behavior. If you're in the market for ideas, I suggest "while" because it has formerly made clear that you'll be iterating while something is true and in this case, the multi-while, you are iterating while any of the guards are true.

This could be interesting but is surely a Go 2 research question. It would not break any existing code and thus is clean in that sense. It is nothing more than syntactic sugar for the code pattern above, but if you can share examples of how you would use it to simplify logic and show how that would lead to cleaner and better-maintained software then that would be a helpful next step.

Here is an example (using "while" as a name) from EWD's predicate transformer semantics (also with a direct lineage to SNOBOL):

func gcd(a, b int) int {
    while {
        a < b: b = b - a
        a > b: a = a - b
    }
    return a
}

this is certainly prettier than any way of expressing it in C, C++, Java, or Go.

Sorry if I've misunderstood. If so, correction is welcome. (It's 2:45am here and I'm a bit groggy. ;-)

Best,
Michael 
--
Michael T. Jones | Chief Technology Advocate  | m...@google.com |  +1 650-335-5765

Jan Mercl

unread,
Mar 31, 2012, 6:09:47 AM3/31/12
to golan...@googlegroups.com, Steven Blenkinsop, lucio
On Saturday, March 31, 2012 11:45:29 AM UTC+2, Michael Jones wrote:
Here is an example (using "while" as a name) from EWD's predicate transformer semantics (also with a direct lineage to SNOBOL):

func gcd(a, b int) int {
    while {
        a < b: b = b - a
        a > b: a = a - b
    }
    return a
}

this is certainly prettier than any way of expressing it in C, C++, Java, or Go.

Don't know if "prettier":

func GCDUint32(a, b uint32) uint32 {
        for b != 0 {
                a, b = b, a%b
        }
        return a
}

(https://github.com/cznic/mathutil/blob/master/mathutil.go#L36)

Michael Jones

unread,
Mar 31, 2012, 6:13:54 AM3/31/12
to Jan Mercl, golan...@googlegroups.com, Steven Blenkinsop, lucio
Nice! Even better. I was trying to help him think of an example that would be good with that "iterate to a fixed point" type of logic. GCD was the simplest I could think of. Maybe too simple. Your nice counter example has me thinking what the best short example of the "while" mechanism might be. Hmm...

Lucio De Re

unread,
Mar 31, 2012, 6:26:52 AM3/31/12
to Michael Jones, Steven Blenkinsop, golang-nuts
I think you have understood correctly. My motive may well be purely a
theoretical one, I happen to find the inconsistency of break or
fallthrough inelegant, as either way the behaviour is dependent on
code position; imagine if the compiler had to re-arrange the code for
whatever purpose.

Once you accept that the construct needs to be self-consistent, you do
need a different approach. But I also think that the Go technique of
merging initialisation into the construct is very clever and deserves
to be applied here. Also, Dijkstra could not have envisaged the idea
of including in the header the target to be met, as is done in the Go
switch statement, a boolean guard statement is plenty, but Go's
approach is much richer.

So, to continue the discussion, I'd say that the command may well be
named "do" in honour of Dijkstra's original design; that it should
resemble a looping switch, specially regarding its header, where local
variables may be initialised and the nature of the target to be
matched can be described.

Somehow I'm hoping that someone will address two issues I haven't yet
found a solution to yet. The first is obvious from current
discussion: there needs to be an additional component to each guard
that can be used to select that particular section as a possible
target from another section. This could be explicit in the guard
itself, but then we add an overhead that looks ugly, is prone to error
and is onerous for the programmer; we could label each section, but
that is monstrous; so we need something really clever, or just a
different representation for non-linear flow.

The other is what I call the "search quandary", where on completion of
a loop you need another test to figure out which of two outcomes
caused the loop to terminate (e.g.: did you run off the end of the
list, or did you actually find the wanted value in the list?). Using
a looping construct that allows selection of the next section to test
and by trivial extension provides sections that on completion cause
the loop to terminate, we can represent a search operation in a single
construct, a construct that will hopefully cover many less elegantly
expressed tasks.

I hope I'm not being unduly arrogant in suggesting that it is in a new
language like Go that such a construct is most likely to appear. I do
believe that there is scope for discussion and that the evolution of
Go, guided by experienced and competent designers, has created fertile
ground for the invention of a construct I myself still don't entirely
grasp. It appeals to me that the end result is likely to be a
simplifying construct: it may be hard to design and implement, but if
it isn't easier to use and understand, it will serve no purpose. My
belief is that it will be both of these.

Lucio.

unread,
Mar 31, 2012, 7:13:10 AM3/31/12
to golan...@googlegroups.com, lucio...@gmail.com
On Friday, March 30, 2012 6:22:11 PM UTC+2, lucio wrote:
The reality is that in either model, we are constrained by the linear
nature of the representation of computer programs to one single
possibility: you either drop into the next section or you don't.

Yes, but it improves readability of programs (in most cases).
 
Which means that if you have a single situation that can be
represented in this fashion, you win, but the moment, say, two
distinct case sections should both continue into the same third case
section, there is no mechanism (short of a GO TO?  I don't even want
to contemplate that option) that makes this possible.  It is even more
serious when there are a few such situations within the same switch.

Now, what problem am I trying to solve here? It would seem reasonable,
at the end of each case section, to be able to determine not just that
we can continue with the next section in linear sequence, but rather
to select, possibly dynamically, which section should be selected,
with the default, as is the case in Go, to drop right out.

In structured programming:

- the program first checks a condition and then executes an action if the condition was fulfilled

- in the source code of a program, condition and action are visually separated by language syntax

- there are no GOTO statements

Non-structured programming (such as: programming in assembly language) is harder to grasp quickly because of the lack of visual cues.

Michael Jones

unread,
Mar 31, 2012, 7:14:34 AM3/31/12
to Lucio De Re, Steven Blenkinsop, golang-nuts
Ok, so 'do' then. I should have gone to EWD 472, "Guarded commands, non-determinacy and formal derivation of programs" immediately (http://www.cs.utexas.edu/users/EWD/ewd04xx/EWD472.PDF) for my examples. Here is sorting by pairwise comparison:

// sort a, b, c, d into non-decreasing order
do {
    a > b: a,b = b,a
    b > c: b,c = c,b
    c > d: c,d = d,c
}

I think this is pretty, and combines implicit iteration to a fixed point and multiple assignment in a simple, elegant structure. The clarity would be ruined by the do it yourself version with the for loop, flags, and if tests.

We see the break and fall through notions and the role of code position in the logic differently. It seem a natural expression of a decision tree and a decision tree is a both a redundancy-eliminating prefix-coding of parallel cases (when all cases are mutually exclusive -- no Venn diagram overlap) and also an expression of priority between multiple potential matches when there are multiple true clauses. These concepts are more common than the "all equally viable" scenario which is likely why languages focus on them.

This may not be clear. Allow me to demonstrate.

if x >= 0 && y >= 0 {
   quadrant = 1
} else if x >= 0  && y < 0 {
    quadrant = 4
} else if x < 0 && y >= 0 {
    quadrant = 2
} else {
    quadrant = 3
}

Note that the first two clauses redundantly test on x>=0 and the second two literally (and then implicitly) test on  x<0.

if x >= 0 {
    if y >= 0 {
        quadrant = 1
    } else {
        quadrant = 4
    }
} else {
    if y >= 0 {
        quadrant = 2
    } else {
        quadrant = 3
    }
}

Here we optimize away some of the tests by building a decision tree and presuming a single prefix decision test at lower levels (i.e., lexically later) in the tree. Now in this case the ordering matters both between cases at both levels of hierarchy. That limits the compiler, true, but also (hopefully) limits the complexity for the reader of the code, which in real cases may have complicated expressions in all of the tests. However all of these cases are equally important. If you or the compiler decided to regroup the tests, say by testing on y before x, or by testing for a and y having the same sign first, the result would be the same.

There is, however, very often the additional fact of priority in a decision hierarchy:

if hairOnFire {
   code1
} else if beeInFace {
   code2
} else if sandInShoe {
   code3
}

The same notions apply to switches. 

switch {
hairOnFire: code1
beeInFace: code2
sandInShoe: code3
}

This is the motivating case for the existing logic of switch. The Dijkstra guard system as he meant it with the clauses being order invariant would fail here because when we have fire and sand, we need to deal with the fire first. To do this with guards we need to code something like this:

do {
hairOnFire: code1
beeInFace && !hairOnFire: code2
sandInShoe && !hairOnFire && !beeInFace: code3
}

The cascade of and-clauses is the way to create priority in the predicates. It is general, but complex and error-prone for hand-coding compared to the use of order to indicate priority. For this reason I value the gain of the order-dependence over its inflexibility. 

However, there are cases where iterating to a quiescent case is natural, where order of application is arbitrary, and where cases are entirely independent. These are a natural for your 'do' proposal, but even with it, I'd still want switch to mean "choose between in the priority order that I specify by text order."

LRN

unread,
Mar 31, 2012, 2:32:11 PM3/31/12
to golan...@googlegroups.com, lucio...@gmail.com
For small logical constructs this kind of complexity doesn't seem to give overwhelming readability/writability improvement (we're not even discussing performance of the code generated from it, it's probably not relevant).

For complex logical constructions of this kind you need a state machine. Building a state machine into the language doesn't seem like a good idea to me.

unread,
Mar 31, 2012, 3:26:02 PM3/31/12
to golan...@googlegroups.com, lucio...@gmail.com
On Saturday, March 31, 2012 8:32:11 PM UTC+2, LRN wrote:
For small logical constructs this kind of complexity doesn't seem to give overwhelming readability/writability improvement (we're not even discussing performance of the code generated from it, it's probably not relevant).

I probably should have written: "Yes, but the current Go syntax improves readability of programs (in most cases)."

In other words, I was trying to disagree with the proposed syntactic changes.

si guy

unread,
Mar 31, 2012, 5:04:42 PM3/31/12
to golan...@googlegroups.com
What about an extension to the if syntax? One that doesn't break existing code.

Something like this, where every "case" is evaluated in order and execution can be terminated with break or restarted with continue.

If {
case A == true:
//do something
If someCheck() { break; } // this breaks out of the switch
case SomeOtherCheck():
continue // this is a goto jump back to the start of the if block
}

unread,
Apr 1, 2012, 4:11:30 AM4/1/12
to golan...@googlegroups.com
Looping in Go is done via "for" statements, recursion, and "goto" statements. It would be wrong to enable loops via "if" statements.

Lucio De Re

unread,
Apr 4, 2012, 2:47:46 AM4/4/12
to Michael Jones, Steven Blenkinsop, golang-nuts
On 3/31/12, Michael Jones <m...@google.com> wrote:
>
> However, there are cases where iterating to a quiescent case is natural,
> where order of application is arbitrary, and where cases are entirely
> independent. These are a natural for your 'do' proposal, but even with it,
> I'd still want switch to mean "choose between in the priority order that I
> specify by text order."
>
Given that the order is there, on the screen (paper, for old hands
like me), I see no harm in accepting it, if only because otherwise the
naive reader is going to be confused.

But in my opinion there ought to be a visibly distinguishable
construct where order is not obeyed, but the relationship between
sections is stated explicitly and possibly (preferably) dynamically.
And this does resemble implementing a state machine in the language,
which some have stated is a bad idea, but seems to me, if done
properly, to be an asset.

Anyway, I'm happy to discuss this idea with somebody who clearly
understands the issues better than I do, I make no apology for not
having an adequate theoretical background, I just promise to listen
attentively enough to learn something from it :-)

Lucio.

Michael Jones

unread,
Apr 4, 2012, 11:35:19 AM4/4/12
to Lucio De Re, Steven Blenkinsop, golang-nuts
Hi Lucio.

I've been thinking about this. I consulted my library at home for all my Dijkstra/Hoare/Wirth books, my history of programming languages books, and my SNOBOL books. I've been looking for examples of the guard-clause Dijkstra-do in code that makes strong argument for the feature. I've not found them so I've been thinking about various kinds of code that people write (calendric conversions, numerical intrinsics, etc.) in search of situations where this would be motivating. I've not yet thought of something stromg there either. These lapses are mine and do not say anything bad about the concept, but I do want to find the ten best cases for this construct.

I realized a few implementation issues while I was doing the above:

1. Dijkstra sometimes uses true and false as guards, true to force an evaluation and false for comments/symmetry/expository clarity. If Go had a 'do' as you suggest, we'd naturally have his true and false.

2. Dijkstra did not have a "default" clause (i.e. "if again == false" in my translation) but we could if we wanted. Not sure if this makes sense, but if it did, what should its effect on "again" be? Should a default clause mean that the do always iterates just once (again stays false even though code executed) or perhaps the default clause should set the flag so that the implicit loop will continue. If so, this is kind of strange because a do with a default would then never end...

3. ...Break and Continue. Should breaking out of the do be allowed by explicit command ("break")? (You must allow this if you allow default.) Should jumping back to the start of the loop and bypassing any remaining-to-be-tested-and-evaluated clauses be allowed by the use of continue? These are easy and consistent but are they clear in the context.

The real strength in Dijkstra's do was being able to label/structure a loop with it's invariant as a basis for asserting program correctness. The genius there is in mechanisms for talking about code more than implementing the code, though they are one and the same in this instance. I feel that we need to show some code examples to have a better argument for the feature.

Lucio De Re

unread,
Apr 4, 2012, 12:33:10 PM4/4/12
to Michael Jones, Steven Blenkinsop, golang-nuts
I'm not sure I'm happy about causing you to spend so much time on what
was initially purely a rebellion against the linear nature of the C
switch, ameliorated, but not eliminated, by the better Go version.

Your note, however, is very illuminating and I really thank you for
that, I guess this is very much what I was looking forward to, without
necessarily knowing it. I do feel a little awed by the interest, and
pleased that I am not so ill informed as to voice totally inane
opinions. As I mentioned, my theoretical knowledge is not very sound.

Regarding the "default" clause, it seems helpful to me to be able to
summarise all exceptions under one heading, but one must keep in mind
that in Dijkstra's model failure to meet at least one guard condition
was the sole mechanism to exit the do construct. As you mentioned,
introducing a default also forces the addition of a break command
(consistent with Go, but not with Dijkstra's design), I'm not sure
about continue, but there would be cause to bring that in as well, I'm
sure.

I thought I had already considered these - I voted for a default
without realising the consequences - but now it is not so clear
anymore. I'll need to spend more time on that, but I confess you have
done a lot better than I in the last few exchanges. To answer your
question, though, at the very least default would terminate the
repetition, in my opinion, providing the escape route I'm looking for
in the "search quandary", but not quite fulfilling all the
requirements.

It seems to me that a complete design would include instructions at
each section exit to navigate within the loop or to terminate,
basically a layer of instructions in some way orthogonal to the
guards. I guess break and continue always provided a blunt version of
what I'm now hoping to refine. The difficulty, of course, is to
provide a tool that is not so sharp the programmer lands up losing a
finger or two to it. But the discussion so far does suggest looking
for two textures and being able to blend them or separate them as
required by the problem in hand. Making this a practical construct
will not be easy, possibly not even helpful, but thinking about it
can't possibly be harmful.

Here, as I did in discovering Plan 9 many years ago, I have to thank
the same people for providing a fresh platform for novel thought and
making this type of speculation worthwhile.

It reminds me of the mental shift discovering and using APL caused me.
I have to mention that here, because my esteemed friends from Bell
Labs are no fans of APL (at least insofar as I have been able to
establish from the occasional email exchanges where the subject came
up) and I can't just continually praise them, I have to poke them in
the ribs occasionally, too :-)

Anyway, this discussion has been very good for me, I'm looking forward
to wherever it eventually takes us and I am thankful that it is still
resonating with others. Maybe the time has come to shift it to
private mail, maybe not. There are a few items I would like to see
discussed, although resolution is still outstanding more or less
across the board and bringing these into play will almost certainly
murk the waters even further.

The Go preambles in the if and switch constructs, together with the
extension of the scope of the constructs to the preamble, are a very
clever, "outside the box" invention. I'd like to think that they
ought to apply to a do construct too, but I can't quite visualise how
they would work.

Hm, I thought I had more up my sleeve, but right now this is all that
comes to mind. Looking forward to more input, definitely, hopefully
I'll be able to continue to contribute. As I mentioned, I will not be
disappointed if the discussion does not lead to a Go extension, I have
already gained and learned much from the exchange so far.

Lucio.

Reply all
Reply to author
Forward
0 new messages