I’m aware this most likely has been asked before, but I couldn’t find it.
I have the following code, which expresses a very simple random generation capability:
//> using scala 3.8
trait Rand:
def nextInt(max: Int): Int
object Rand:
def int(max: Int): Rand ?-> Int =
handler ?=> handler.nextInt(max)
def listOfN[A](n: Int, randA: Rand ?=> A): Rand ?->{randA} List[A] =
List.fill(n)(randA)
def apply[A](body: Rand ?=> A): A =
val rand = scala.util.Random()
given Rand = max => rand.nextInt(max)
body
The important bit is how listOfN takes an effectful computation - a Rand ?=> A and not an A, and how eager evaluation of context functions can cause programs to type check but not behave the way they’re intended to at all.
Now, to generate a random list of integers, one merely needs to run:
Rand:
Rand.listOfN(4, Rand.int(5))
This compiles, runs, and evaluates to something like List(1, 4, 0, 3). No surprise so far, this is exactly what I would expect.
For some reason, I may come back to this later and decide to extract Rand.int(5) - maybe it’s a little too noisy:
Rand:
val content = Rand.int(5)
Rand.listOfN(4, content)
With the way context functions are applied, combined with automatic conversion to context functions, this:
- pulls a random number, let’s say 2, and binds it to
content. - transforms
contentin thelistOfNcall to(rand: Rand) ?=> content- in our example, the effectful computation that returns the constant2.
We always get random lists of size 4, but that always contain the same element - List(2, 2, 2, 2) in our example.
Of course, this all makes sense if:
- you have a fairly good grasp on context function and some of their (as of yet undocumented?) features.
- know all the types and realise that
Rand.listOfNtakes an effectful computation.
But I would argue that this is not always going to be the case - my gut feeling says it’s more often than not not going to be the case.
I also know you can disambiguate with explicit type ascriptions: val content: Rand ?→ Int = Rand.int(5). But in order to do that, you need to know that the problem exists, and I think the bad part of this problem is that most of the time, you won’t realise this is going on. I didn’t.
Is there some way of working around this, some compiler flag to warn, some bit of syntax I’m not aware of?