Fork { ... } vs. { ... }.fork

So I’ve finally figured out that fork { ... } is shorthand for {...}.fork
with the receiver as “trailing argument” to its method (as per the syntax shortcuts helpfile)… again, one of those things where I wonder why it took me so long…

Other examples of the trailing block argument syntax are not of methods of a function instance, but rather of methods that take functions as arguments: collect ((1..3)) { ... }.

So I’m curious what the reasons are that examples in the docs regularly use the fork { ... } syntax rather than {...}.fork but don’t do the same thing e.g. with the play method (play {... }). (I have seen a few instances of loop { ... }, though.) Are there historical reasons, is there some other language that does this sort of thing and from which supercollider inherits? Why fork and loop in particular?

All of this with a view to eventually putting a note in the docs somewhere that explicitly points to this alternative syntax for fork. It had been confusing me for a while…

It’s really just a matter of personal preference.

Generally, sclang has uniform function call syntax, so f(g) is the same as g.f. This means that you can either write wait(5) or 5.wait. This works with every method (including class methods!) so you can commit syntax crimes like read(Buffer, s, "sounds/a11wlk01.wav") *)

When you combine this with the trailing block argument syntax, you get quite a few possible combinations:

  1. if (b) { ... } { ... }
  2. if(b, { ... }, { ... })
  3. b.if({ ... }, { ... })
  4. b.if { ... } { ... }
  5. if (b, { ... }) { ... } // ugh!
  6. b.if({ ... }) { ... } // ewww!

As for your question why people seem to prefer function call syntax (with trailing blocks) for certain methods like if, fork or loop: I guess it’s just clearer to have the method at the beginning. If you put .fork at the end of a long block, readers have to scroll all the way down before they know what’s going on. For if-statements in particular the function call style just looks more familiar to other languages with C-style syntax (C, C++, JavaScript, Java, C#, etc.)

*) Yes, that’s really the same as Buffer.read(s, "sounds/a11wlk01.wav")!

2 Likes

Thanks!

Yeah, I figure looking vaguely like C had to do with it. The tricky bit with the doc situation with respect to fork is that, unlike for if and loop, there is no dedicated reference page, and it doesn’t show up on the syntax shortcut page; only as an instance method in class docs. So in my case I when searching it I thought, “uh but I’m looking for a keyword, not a method”, so I didn’t look at the doc entry for the Function.fork() instance method until a few days ago…

[makes me wonder: are there any genuine keywords apart from var, arg and this in sclang? I.e., keywords that cannot be expressed as methods.]

I’ve thought about that “end of a long block thing”, but that hasn’t deterred people from writing long blocks on which to call .play at the end…

FWIW, upon digging further in stackexchange, there are people that claim fork has more to do with unix than with C. I wonder if that has any bearing on the use of that trailing argument syntax? (As in a shell command I imagine.)

There is also thisThread and thisProcess, but that’s about it, I think. sclang follows the Smalltalk tradition where basically everything, even control flow, is implemented with methods.

I’ve thought about that “end of a long block thing”, but that hasn’t deterred people from writing long blocks on which to call .play at the end…

Fair enough.

FWIW, upon digging further in stackexchange, there are people that claim fork has more to do with unix than with C

Doesn’t sound very convincing to me.

Actually, I’ve seen both fork {} and {}.fork in the SC Class Library, sometimes even in the same file! It’s really just a matter of personal preference. Don’t worry too much about it!

1 Like

You’re right, somehow I was under the mistaken impression that fork {} was being used much more often in the docs, but I just checked and it’s 84 instances of that vs. 64 of .fork… so whatever. Sorry for wasting people’s time (insert appropriate emoji).
I think I misrepresented the thing in stackexchange, that has nothing to do with the syntax but was about whether fork() is in the C standard library (it is) and whether it’s part of standard C (it’s not), and where it historically came from (more unix than C, but also Bell Labs at a time when C was in the making there as well, it seems — cue picture of two bearded 70s programmers. So that’s complicated).

One difference is that in do and collect etc., generally the function is the last argument – so it’s always a “trailing block” and there is seldom a compelling reason to write e.g. array.do({ ... }) rather than array.do { ... }.

With fork, however, it’s common to want to fork a routine onto a specific clock. The clock argument follows the function receiver, so in that case, it’s impossible to use trailing-block syntax.

fork { ... }   // OK

fork { ... }, AppClock  // no

fork({ ... }, AppClock)  // OK but not trailing block syntax

{ ... }.fork(AppClock)  // OK

So perhaps the syntactical variety arises from the different use cases.

hjh

2 Likes

whether fork() is in the C standard library (it is)

Nitpick: it isn’t. fork is defined in unistd.h which is part of the C POSIX library (see unistd.h - Wikipedia). The C standard library is actually specified by the C standard (see C standard library - Wikipedia). The C POSIX library is a superset of the C standard library.

1 Like

See, that’s how confused I am!

If you’re interested: despite the fact that the same thing can be written in approximately a quadrillion ways in SuperCollider, there’s actually guidelines that point out syntaxes that should be preferred. However, fork isn’t discussed.

2 Likes