I've used ScalaZ a bit (don't remember exactly why, but something to do with Future transformations), but I found it to crush the compiler. Especially combined with IntelliJ.
I like the async/await stuff. Especially after attending the ScalaDays presentation on it. The idea that it produces a state machine in the background feels like it's very easy to reason about.
I actually (personally) find for-comprehensions the least useful feature of Scala probably. They rarely produce the most readable code IME with just a couple transformations in play, and it's not often I find myself dealing with compatible types in the more complex cases.
So I guess I consider async/await the readable/prettier alternative to direct mapping that for-comprehensions mostly fail to deliver on. for-comprehensions are probably Scala's second biggest wart IMO (not harmful, more just mostly useless). YMMV. Sort of like `__DATA__` or `=BEGIN/=END` in Ruby.
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.
I like the async/await stuff. Especially after attending the ScalaDays presentation on it. The idea that it produces a state machine in the background feels like it's very easy to reason about.
I actually (personally) find for-comprehensions the least useful feature of Scala probably. They rarely produce the most readable code IME with just a couple transformations in play, and it's not often I find myself dealing with compatible types in the more complex cases.
So I guess I consider async/await the readable/prettier alternative to direct mapping that for-comprehensions mostly fail to deliver on. for-comprehensions are probably Scala's second biggest wart IMO (not harmful, more just mostly useless). YMMV. Sort of like `__DATA__` or `=BEGIN/=END` in Ruby.