For comprehension is probably Scala's most powerful feature aside from higher kinded types.
You may not see the advantage of for comprehension for sequencing a few operations over Future. However, when you have a large number of calls you have to sequence along with filter (which for comprehension can do) it's indispensable.
I disagree. I can only talk about f# but for me the comprehension syntax is mostly an aesthetic choice:
[1..100] |> List.filter (fun e -> e % 2=0)
|> List.map (fun e -> e * 2)
or:
[for e in 1 .. 100 do
if e % 2=0 then
yield e * 2]
Personally i prefer the first one since it's compositional and more like a dataflow. But i am not familiar with Scala so maybe i am missing something.
I've actually gotten to the point where I think for comprehensions are a code smell. The only time I prefer the for syntax is when I have a large list of monad chains.
Large lists of monad chains almost always indicate some sort of poor factoring of the code.
I'm curious why you'd call for-comprehensions powerful though. AFAIK they're just sugar over map/flatten/filter.
IME it's almost always more succinct and more readable to just call the methods you want directly.
Plus, you can say: map over an Option and transform both cases. You could also map then getOrElse, but readability suffers if your map is multi-line IMO. In the for-comprehension version you can't transform the None case.
I use for-comprehensions with Extractors in testing, because whatever. It's a test. So:
for {
Some(user) <- db.get[User](userId)
} yield otherStuff
Is fine in that case.
Pattern Matching and Lifting are probably Scala's best features off the top of my head. Type Classes a close third.
But for-comprehensions are just sugar. They don't enable you to do anything you couldn't without them, and they actually make some flows impossible to write. I find that you can usually tame a nested mess with partial functions and a collect(). Or a fold() to handle your dual-transform.
That's just me though. Only been at the Scala job for a little over a year.
edit: @noelwelsh
I would nest yeah. But I'd see it more as a refactoring opportunity. Should authorization be in a for-comprehension? I'd instead add an AuthorizedAction in Play. That authenticated and provided a User from cache. So your example would look more like:
I think I'd have to agree with another poster that doing all that inside of a for-comprehension would look like a code-smell to me.
More than that, is map/flatten the right tool for the job for all this? Even if I wanted to do it inline, I'd probably prefer:
val perm = loginActions.mandatoryAuth(req)
val queryString = req.mandatoryParam[String](uuidParam).toClientProblem
(perm zip queryString) map {
case (Some(perm), CachedUser(user)) => actions.user(Read(perm, user))
case _ => BadRequest()
}
It's definitely subjective. I wouldn't fault anyone for using the for-comprehension (though I would encourage them to consider if it should rather be an Action), but describing it as "powerful" just doesn't sit right with me for some reason.
Plus while you'll see for-comprehensions in the wild on occasion, I think it's a stretch to call them idiomatic. Unless you were going to constrain yourself to projects with ScalaZ as a dependency I suppose.
When you're doing functional programming you represent (almost) everything as a value. Say you're working in a concurrent system, (e.g. a web app) so you're dealing with Futures everywhere. Are you going to write 4 or 5 nested flatMaps? It's unreadable. For comprehensions are much easier to parse. Here's an example from real shipping code
for {
perm <- loginActions.mandatoryAuth(req)
queryString <- req.mandatoryParam[String](uuidParam).toClientProblem.fv
user <- stringToUser(cache.user, queryString).fv
result <- actions.user(Read(perm, user))
} yield result
Then you get into nested monads (e.g. Either can represent a computation that succeeds or fails, which you want to contain inside a Future) and you use monad transformers to squish them into one single monad, to avoid nesting for comprehensions.
You may not see the advantage of for comprehension for sequencing a few operations over Future. However, when you have a large number of calls you have to sequence along with filter (which for comprehension can do) it's indispensable.
Check this out: https://www.youtube.com/watch?v=MHw-dDxC8Z4