It would be awesome if Scala improved upon the current state of for-comprehensions.
We don’t have to look far for inspiration, we can do something similar to what F# is doing with so called Computation expressions
For Scala the code then could look something like this
def userLogin(username: String, password: String): Future[UserInfo] = for
val dbReq = CheckUserLoginRequest(username, password)
val! userId = userDb.login(dbReq)
val! profile = profileDb.fetch(userId)
val! avatar = avatarDb.fetch(userId)
yield UserInfo(profile, avatar)
A general de-sugaring table could be like this
ordinary code analogue
for-comprehension
sugar for
val ... = ...
val! ... = ...
(legacy ... <- ...
)
flatMap
if ... then ... else ...
if! ... then ... else ...
ifM
if ... then ...
if! ... then ...
whenM
throw ...
throw! ...
raiseError
try ... catch ...
try! ... catch ...
handleErrorWith
while ... do ...
while! ... do ...
whileM
use ... = ...
(hypothetically)
use! ... = ...
use
…
The point here is to
enable as many analogues to syntactic constructs from ordinary code (val
, try
, if
, …) as possible
make them look as similar to ordinary code constructs as possible. Appending !
is one possible way to do that. val!
is better analogue to val
than <-
can ever be.
Previously discussed on this forum:
I feel like that we need to improve the for-comprehension itself. It should be generalized to match the other syntactic/control flow constructs in Scala.
The regular Scala constructs, like val, if, throw would have their counterparts with !, like val! (currently <-, flatMap), if! (ifM), throw! (raiseError), etc…
The beauty of this approach is that it builds on top of the already existing concept of for-comprehensions and uses similarly named keywords for analogous things (there is just ! appen…
F# is really nice and simple language, not as powerful as Scala, but still very well designed. Two things that immediately come to mind that F# does better than Scala:
I have to start with syntax. Syntax is the least important aspect of a language, but F# is just so nice. It’s simple, regular, and very light. It has the most crisp syntax I have ever seen, better than Python, Haskell, Scala or whatever else. Scala (and all other languages) should learn from it: https://fsharpforfunandprofit.com…
I think that whenever we discuss async/await-like programming or for-comprehension in Scala, we should take a step back and have a look at F#'s Computation Expressions .
They are a variant of Haskell’s do-notation or of Scala’s for-comprehension, but it’s much more generalized and customizable. It doesn’t work only with flatMap <- (as if “monadic” val x = ...) and pure yield, but also works with for/while loops, sequences, pattern matches and resource disposal and try-catch-finally analogs from …
Interesting take! But I think we don’t really need to copy F# verbatim here. F# does certain things the way it does, because it lacks other features that Scala possesses even today. Needing to have an explicit “builder” object is one such example. In Scala, we have implicit parameters, extension methods and higher-kinded types. We should do fine with only those. What we would need, is a new do construct.
Let me illustrate it with this short snippet:
def fetchAndDownload(url: Url): IO[Data] =
…
+1 for embracing fully F#-like computation expressions
But the problem with the current for comprehension and redundant map could be solved by a relatively small change:
What we need is a for comprehension without the yield part. So instead of
for {
a <- f1
b <- f2(a)
c <- f3(a, b)
} yield c
it would be just
for {
a <- f1
b <- f2(a)
f3(a, b)
}
That’s how Haskell does it, btw. Allowing that seem easily doable, doesn’t it? Or are there problems with it?
On Reddit:
https://old.reddit.com/r/scala/comments/y6zyx9/the_case_against_effect_systems_eg_the_io_data/#ist9r0f
5 Likes