Hey, I am sure this already has been brought up, but I think for is trying to serve too many masters. There is collection heavy code with filtering and such and then there is monadic code. And while there exist a big overlap between the two, maybe it is still worthwile considering the two in isolation.
I, for myself, am writing a lot of monadic code recently and noticed that for is really getting into my way in doing so. Here is a snippet that illustrates some of the pain-points:
for {
cur <- pos.value
cacheKey = (key, cur)
cache <- resultCache.value
res <- if (cache isDefinedAt cacheKey) (for {
(results: A, newPos) <- pure(cache(cacheKey))
_ <- pos.update(newPos)
} yield results) else { ... }
} yield res
With some form of do-notation this could be written as
do {
val cur <- pos.value
val cacheKey = (key, cur)
val cache <- resultCache.value
if (cache isDefinedAt cacheKey) do {
val (results: A, newPos) = cache(cacheKey)
pos.update(newPos)
pure(results)
} else { ... }
}
I am purposefully using do here to clearly distinguish from the existing for.
Note that val ID <- EXPR translates similar to ID <- EXPR in for and val ID = EXPR translates similar to ID = EXPR in for. To be more compositional, the last value in the do-block has to be monadic, hence the pure(results) in the translation to explicitly lift results. At the same time
[[EXPR1; EXPR2]] translates to val _ <- [[EXPR1]]; [[EXPR2]].
That is, sequencing is monadic sequencing. This makes “builtin side effects” more cumbersome to use via val _ = println(...). However, I feel this overhead is appropriate since do switches citizenship between builtin side effects and managed side effects.
I am aware of monadless and other approaches to ease monadic programming, but I couldn’t find a dotty SIP.
@odersky You mentioned, you don’t want to go back to vals. Would that also hold if one separates for and an alternative do-notation? I am purposefully using vals here to highlight that do blocks “just” overload sequential composition.
edit: Thinking about it a bit more: If we ignore overloading of ; for a moment – only having val f <- EXPR to translate to flatMap which scopes over the rest of the current block would already go pretty far.