Noob: don't understand usage of curly brackets in if-statement

This got a bit intense…

I didn’t think of it earlier today, but one way to distinguish between flow of control statements (in the C/Java sense) and method calls is: method calls (in SC) always have a return value (SC has no void methods), while AFAIK if in C has no return value (you can write x = condition ? trueValue : falseValue in C, but not x = if(condition) trueValue else falseValue – where SC supports x = if... and not the former).

This is one reason why I think in SC they are fundamentally method calls, which may under certain conditions be optimized by inlining. Those conditions are common but not guaranteed, so it isn’t right to expect every if to be inlined.

Inline-optimizable methods are if, while, switch and case. To my knowledge, no others are inlined.

There’s no C-style block in SC. Braces delimit first-class functions. It’s a bit confusing if you approach SC from the perspective of “it looks kinda like C, so I’ll treat it like C.” SC isn’t C! Thinking of it as a C derivative will only get you into trouble.

I realize that my statement “there are no control structures in SC” is a bit radical – but this is intentional: to jolt the reader out of carrying in assumptions from C. (It was a major light-bulb moment for me in SC to realize that SmallTalk’s design totally obviates the need for control structures. Branching may be handled by polymorphic dispatch; looping, by recursion. C is so prevalent that one might assume it’s impossible to have a high level language without control structures, but that assumption is not true. In SC, inlining is an optional speed optimization only.) Subsequently, much of this thread has been critical of SC for failing to match C – i.e., missing my initial point.

hjh

Thanks, but then I have to admit that I still don’t get, unfortunately. Please have a look at this:

// Cases A: true/false functions enclose a value
// Please evaluate line by line
~wahr = {1};                                    // -> a function
~falsch = {0};                                  // -> a function

~wahr;                                          // A1: -> a function
~wahr.();                                       // A2: -> 1
~wahr.value();                                  // A3: -> 1
{~wahr};                                        // A4: -> a function
{~wahr.()};                                     // A5: -> a function
{~wahr.value()};                                // A6: -> a function

if (false, ~wahr, ~falsch);                     // A1 (functions): -> 0
if (false, ~wahr.(), ~falsch.());               // A2 (values):    -> 0
if (false, ~wahr.value(), ~falsch.value());     // A3 (values):    -> 0
if (false, {~wahr}, {~falsch});                 // A4 (functions): -> a Function (unexpected)
if (false, {~wahr.()}, {~falsch.()});           // A5 (functions): -> 0
if (false, {~wahr.value()}, {~falsch.value()}); // A6 (functions): -> 0

// Cases B: true/false functions enclose a function posting a value
// Please evaluate line by line
~so_true = {"1".postln};                         // -> a function
~so_false = {"0".postln};                        // -> a function

~so_true;                                        // B1: -> a function
~so_true.();                                     // B2: 1 -> 1
~so_true.value();                                // B3: 1 -> 1
{~so_true.()};                                   // B4: -> a function
{~so_true};                                      // B5: -> a function

if (false, ~so_true, ~so_false);                 // B1 (functions): 0 -> 0
if (false, ~so_true.(), ~so_false.());           // B2 (values):    1 0 -> 0 (unexpected)
if (false, ~so_true.value(), ~so_false.value()); // B3 (values):    1 0 -> 0 (unexpected)
if (false, {~so_true.()}, {~so_false.()});       // B4 (functions): 0 -> 0
if (false, {~so_true}, {~so_false});             // B5 (functions): -> a function (unexpected)

The cases B2 and B3 are still impossible to understand for me, whereas I hope I understand the cases A4 and B5 now that I write this down. Please let me explain:

B2 and B3: unconditional behavior

  • In B2 and B3, the evaluation of the arguments gives values, so both (!) values are unconditionally posted – to me this means that the if message is not working.
  • Now compare this to A2 and A3: Here the evaluation of the arguments also gives values, but in these cases, only one value is conditionally posted – the if message is working.
  • Why does if work in A2 and A3, but not in B2 and B3, although in all four cases values are handed over? I am afraid to say I don’t understand it, I fail to see the “pattern” in this, it seem arbitrary to me (I guess now I understand how folks learning German must feel).

A4 and B5: functions as results

  • In A4 and B5, the evaluation of the arguments gives functions, and only one function is conditionally posted (so if works), but it is not evaluated.
  • It is not evaluated as I wrapped the function into curly brackets, so the evaluation the curly brackets gives “yes, this is a function”, then the whole thing (curly brackets with the function inside them) is passed to if, where it gets executed with the result that the actual function inside the curly brackets is not evaluated, as the evaluation only exectued “revealing” the function by executing the curly brackets.
  • Is my explanation sort of correct, at least in layperson’s terms?

I am sure I am just missing something important, sorry to be so ignorant. And thanks again in advance for any help!

P.S. What does “the compiler will inline it” mean in layperson’s terms?

No, it means that the if construction hasn’t been written correctly for the desired behavior.

if(something, ~wahr.value, ~falsch.value) actually means to evaluate them both and pass those results into the if method. This unequivocally does not mean conditional execution in SC! It does mean, though, a conditionally-determined return value from if (see below).

This is why I disagree with the attempts in this thread to characterize if as a control structure in SC – because this leads to misunderstanding of how if works in SC.

Only one is posted because if returns only one of them. They are both evaluated, but if they don’t post, then they evaluate silently. In B, both functions post within the functions, so you can see that they both evaluate.

The behavior is consistent: in both the A and B cases, both functions evaluate. It’s only hidden from you in the A case.

Again: control structures don’t have return values, but method calls do. The if return value is critical to understanding this case.

IMO you’re overcomplicating it. The meaning of the braces is simply to denote a function object.

In layperson’s terms, it isn’t important. It means faster execution, nothing more. As Christof said, inlining doesn’t change behavior – it must not change the meaning of the code! So you don’t have to think about it.

hjh

1 Like

This is completely correct.

The compiler inlines if all arguments are literal functions (I think switch allows literal values too), and none of the functions declares local variables or arguments. That decision is made 100% based on the contents of the syntax tree generated by the parser, which should be identical whether the literal functions are within or outside the parentheses.

hjh

1 Like

Thank you very much, now I (hopefully) understood what you meant with “there is no conditional execution in SC”: The double postings I saw were not the result of a “failed” if message, but just the results of the evaluations of the trueFunc and falseFunc, which always happens, because both need to be evaluated before being handed over to the if message.

So when I execute this…

~so_true = {"1".postln};
~so_false = {"0".postln};
if (false, ~so_true.(), ~so_false.());

…which gives this result in the post window…

1
0 
-> 0

…the line -> 0 is what if returns, whereas the lines 1 and 0 are the results of the necessary evaluation of the trueFunc and falseFunc (necessary in the sense that these evaluations have to happen prior to their results being handed over to if), and as these functions consist of .postln, they do just that: they post the 1 and the 0. If they

Is this correct?

P.S. I have to admit that due to myself not having any experience in C, I somewhat rushed over your sentences about SC not having conditional exectution. In fact I don’t have any experience with any other language except SC. But what I have in mind is a basic theoretic concept that if statements allow to take different pathways thru a piece of code, and this was obviously enough to throw me off course. Thanks for taking the time to explain.

1 Like

I think you got it now :slight_smile:

1 Like

You’ve got most of if right except this bit

if still chooses what to return even in that case, what if can’t do in that case is choose what to execute as a side-effect, because that happened already before if got control.

More precisely, for example, this asymmetric bit:

z = if(true, x = x + 1, { y = y + 1 })

is executed as if you’d had written

x = x + 1;
z = if(true, { x }, { y = y + 1 })

if still chooses here what to return between the branches, i.e. what shall become the value of z, but the value of x is pre-incremented before if gets to decide anything. Likewise

z = if(true, x = x + 1,  y = y + 1)

is the same as far as observable program behavior goes as writing

x = x + 1;
y = y + 1;
z = if(true, { x }, { y })
1 Like

About learning Sc, there are some nice books about Smalltalk (of which Sc is a dialect) at:

http://stephane.ducasse.free.fr/FreeBooks.html

The “Blue Book” (for instance) is rather good (conditionals are on page 34)

https://rmod-files.lille.inria.fr/FreeBooks/BlueBook/Bluebook.pdf

3 Likes

One more thought (and I didn’t think of this until this morning) –

It can be useful to think of method arguments being resolved before calling the method, rather than evaluated.

For method call receivers and arguments:

  • Variables resolve to their values: ~wahr resolves to its value (the function) and the function is passed to if.

  • Method calls (including math operators) resolve by executing the method and using the result. ~wahr.value(1) without brackets must resolve the value call by running the function, immediately.

  • Functions in braces are already resolved! But, not executed. So { ~wahr.value(1) } needs no further resolution (no action) prior to calling if – one or the other function will be executed within if. (Actually this last point holds for all literal values – functions in braces are literal.)

I think this is the shortest and cleanest way to explain it. The above three points cover all of the cases in SC, and I think there’s no exception to them.

hjh