… doesn’t that make SC like semi-lazy? Since it’s not evaluating g yet …
Yes, that’s exactly the connection!
In SuperCollider conditional evaluation is written something like:
+ True { ifThenElse { arg q, r; q.value } }
+ False { ifThenElse { arg q, r; r.value } }
and then p.ifThenElse({ q }, { r }).
The receiver (p) and the message arguments ({ q } and { r }) are evaluated before performing the message send (c.f. applicative-order evaluation).
Hence the lovely and concise notation for writing procedures of no arguments!
In Haskell conditional evaluation is written something like:
ifThenElse True q r = q
ifThenElse False q r = r
and then ifThenElse p q r.
Haskell won’t evaluate any part of any expression unless and until it’s required (c.f. normal-order evaluation).
Haskell does something quite like rewriting each expression e as { e } and implicitly sending value messages.
In addition it replaces { e } with it’s answer when it’s first evaluated, so the work only happens once, something like:
var memoize = { arg f; var r; { r.isNil.if({ r = f.value }); r } };
var f = { arg c; "f!".postln; 42 };
var g = memoize.value({ "g!".postln; 84 });
[f.value, f.value, g.value, g.value] // prints f! twice and g! once
There are very nice aspects to both normal-order and applicative-order evaluators!
Ps. About:
…in imperative languages with side effects…
Haskell has a very simple mechanism for attaching an effect to an expression (c.f. unsafePerformIO) but in general it’s very hard to know if, or when, or how many times such effects will be evaluated, hence all the libraries (Control.Applicative, Control.Monad &etc.).
Applicative-order evaluation is a very nice sequencing construct!