Pre-SIP: Desugaring If statements

I have a proposal that we desugar if statements in the same manner as the desugaring of for does. This allows if to be customizable in the same way as for, which I think makes Scala more consistent.

Essentially

if (condition) t else f

is desugared into

condition.cond(t, f)

with cond having the following signature:

def cond[A](t: => A, f: => A): R

This might be useful in a few cases. E.g.:
Given a probability monad Stochastic[+_], and if we have a probabilistic Boolean, we could do

val coinFlip: Stochastic[Boolean]
val t: A
val f: A
val r: Stochastic[A] = if (coinFlip) t else f

This is more declarative for probabilistic programming than using a monadic comprehension:

val r = for (isObverse <- coinFlip) yield if (isObverse) t else f

Or in an autodiff system (i.e. deep learning) when we build a computation graph conditioned on the value of a variable:

val c: Symbolic[Boolean]
val t: Symbolic[A]
val f: Symbolic[A]
val r = Conditional(c, t, f)  
// creates a conditional expression whose result depends on c at runtime
// compare `tf.cond` in TensorFlow and `torch.where` in PyTorch

We could make this into a more readable form:

val r = if (c) t else f

I agree that this is rather niche, but in my opinion would be a nice addition.

3 Likes

Regarding this proposal, I would have loved to been able to override if for specific types out-of-the-box, as allowed in Scala Virtualized.

Could you elaborate? What are types that you want if overridden out-of-the-box? I’m curious about the usage in Scala Virtualized.

It’s for a DSL, so we can get information on both branches of the if. Do I need to create a special myIf to handle a MyBoolean condition and return => MyRetType for then/else branches.

1 Like

This is exactly what I have in mind.

1 Like

It would be very useful for DSLs. It’s a shame, little of scala-virtualized has ever made it into the main language.

If the desugaring is handled dynamically, like looking up default values, perhaps the cond signature could also ask for implicit parameters (e.g. context) when needed by a particular DSL.

1 Like

I intend on creating a Virtualized maintained plugin/fork, but I still don’t have the bandwidth for it yet.

It is very annoying that we need to use while instead of for when we need high performance.

So I think simple if is a very good thing. It doesn’t matter how it is named(if \ sif).

It’s a matter of compiler optimisation. You could still have a virtualised while that does not need to create a lambda when the receiver is Boolean. Wasn’t there a plan to optimise for for some cases (such as using a Range)? What happened to that?

1 Like

Ok, just let there be annotation that guarantee it’s optimization.
For example:

@tailrec
def gcd(a: Int, b: Int): Int = …

I do not need the declarativity I want assurance in some cases.

That’s right. In the desugaring of for-comprehensions, you can ask for implicits for map / flatMap.

If it is an actual Boolean in the if condition, I’m sure that the compiler can rewrite it to a simple JVM if. I guess that the same can be done for for (i <- x to y) (as for why it is not rewritten, I don’t know).

I think that the compiler does not have to decide whether it is " actual Boolean" or " dsl Boolean".
it’s too complicated.

Of course the compiler should make optimization. But it is a complex task in general, for example Virtualized-Scala-Reference. So I prefer to have guarantee that neither compiler nor I make mistake.

I don’t think it’s an issue. The compiler already rewrites a method call a.&&(b) to specialized bytecode when a is a Boolean. It could easily do the same for a.cond(b, c).

2 Likes

May be you are right.
I cannot see the difference between

// Virtualized built-ins
def __ifThenElse[T](cond: => Boolean, thenp: => T, elsep: => T): T

And:

 def foreach[U](f: Elem => U): Unit

So I just do not understand why such optimization(foreach) does not work by default.

Currently I can see, that

@tailrec
def gcd(a: Int, b: Int): Int = …

works fine.

And I can see that for does not work fine in some case.

So I prefer to have anotation which always works fine, than a declaration that works fine in most cases.

I just do not want to make something like

for if

If the compiler guarantees it I will completely agree with you.

cif (foo) bloop celse floop