Pre-SIP/SIP-62 addition proposal - better effect loops with for-comprehensions

Hi, I have a new proposition for improving for-comprehensions in Scala 3.

Motivation

I want to fix the problem that arises from the use of the following code:

//> using scala 3.3.3
//> using lib "dev.zio::zio:2.1.5"

import zio.*

def loop: Task[Unit] =
  for
    _ <- Console.print("loop")
    _ <- loop
  yield ()

@main
def run =
  val runtime = Runtime.default
  Unsafe.unsafe { implicit unsafe =>
    runtime.unsafe.run(loop).getOrThrowFiberFailure()
  }

This kind of effect loop is pretty commonly used in Scala FP programs and often ends in yield ().

The problem with the desugaring of this for-comprehensions is that it leaks memory because the result of loop has to be mapped over with _ => (), which often does nothing.

Proposed Solution

A possible solution that I want to suggest and possibly add to the betterFors language extension is to remove these unnecessary map calls generated from yield () when the last binding also has type Unit.

A possible approach could be to add sticky keys to the map calls generated from for-comprehensions and add a new phase after. That phase would then check for every such map call if the argument is equivalent to _ => () and the previous binding is also of type Unit.

A naive PoC: Crude implementation of removing trailing unit-literal maps from for-… · KacperFKorban/dotty@31cbd47 · GitHub

Discussion

These changes can be introduced as either a separate SIP or as an addition to the SIP-62 (betterFors).

Let me know what you think.

2 Likes