I think you are creating a bit of a false dichotomy here.
Haskell has monad comprehensions built into the language. One reason for that is the use of monads for imperative-style I/O. Therefore, the designers of Haskell decided to make it look mostly like a code block in a generic C-style language, complete with curly braces, semicolons and even return
. The assignments look a bit non-C, but left-arrow for assignment is typical imperative pseudo-code.
So, monad comprehensions in Haskell were designed to look like imperative sequential side-effecting code blocks in some generic C/pseudo-code like language, because that's an important use case for them. This even permeates the API design with the naming of the aforementioned return
function. Note that the name of this function doesn't really make much sense if you use it outside of do
notation.
C# has monad comprehensions built into the language. One reason for that is the use of monads for querying of datasets. Therefore, the designers of C# decided to make it look mostly like SQL or XQuery, complete with FROM
, SELECT
, WHERE
, GROUPBY
, ORDERBY
, etc. The order looks a bit non-SQL (FROM
before SELECT
), but it's the same one as used by XQuery, actually.
So, monad comprehensions in C# were designed to look like SQL queries, because that's an important use case for them. This even permeates the API design with the naming of e.g. Select
for the transformation function (instead of map
), SelectMany
(instead of flatMap
), Where
(instead of select
/ filter
), etc. Note that the names of these operations don't really make much sense if you use them outside of a query comprehension, and in fact, can even be actively confusing (Select
is what is usually called map
, whereas what is usually called select
is Where
).
Scala has monad comprehensions built into the language. One reason for that is the use of monads for collection operations. Therefore, the designers of Scala decided to make it look mostly like a for
loop in a generic C-style language. The assignments look a bit non-C, but left-arrow for assignment is typical imperative pseudo-code.
So, monad comprehensions in Scala were designed to look like imperative for
loops in some generic C/pseudo-code like language, because that's an important use case for them.
Note that both in the case of C# and Scala, the comprehension syntax can actually do more and/or less than just perform the two monadic operations join
and bind
(or map
and flatMap
). In Scala, a for
comprehension without yield
translates into foreach
, i.e. an imperative side-effecting iteration, which really doesn't have much to do with monads at all. A yield
can have a guard (yield foo if bar
), which translates into a call to a call to withFilter
, i.e. filtering elements. C# can do the same with Where
. C# can also do aggregation (group by
) and sorting (order by
).
They are actually more of a generalization and/or fusion of monad comprehensions and list comprehensions generalized to arbitrary collections and monads. Note that Haskell also has both, but they are different things, in C# and Scala, they are fused together.
Haskell has a lot of history, and has pioneered a lot of concepts. As is typical with pioneers, often the people who come after them, discover better, shorter, safer routes. Maybe if Haskell had been designed with hindsight instead of innovation, they also would have generalized list comprehensions to work with more collections, and would have fused monad comprehensions and generalized collection comprehensions together, who knows? I know that there are proposed variants and extensions to Haskell, which add Arrow Comprehensions, for example.
The words "for" and "yield" seem likely to confuse programmers who are used to, for example, Java's for loop and Ruby's yield.
The first one is intentional. It is supposed to look like a for
loop.
The second one is unfortunate. The word yield
has two (actually related but not obviously so) meanings. One is the meaning used in concurrency, coroutines, fibres, threads, and for the yield
keyword and the Fibre#yield
method in Ruby, where it means that a piece of code yields control of execution to another piece of code. The other is the meaning of a computation yielding a result. That's the meaning that is used in Python generators, C# generators, and interestingly also in Ruby, in the Enumerator::Yielder#yield
method, and is the interpretation that is meant for the yield
keyword in Scala for
comprehensions.