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`