I've been hanging out in a #haskell IRC room a couple of days ago and someone mentioned that C# has syntax for doing monadic comprehension. What does this mean?
If I understand correctly, monadic comprehension is just a way of sequencing bind
operations, which kind of sounds like a do
notation? Is that correct?
The problem is that I don't see this in C#. As far as I know IEnumerable<T>
is a monad where SelectMany
is its bind
function, as its signature is A -> IEnumerable<B>
. With a little stretch of imagination, we can do
from x in xs
from y in ys
which translates into (I'm not 100% sure here)
xs.SelectMany(x => ys.Select(y => y), (x, y) => ...)
But even if this is true and we think of LINQ as a monad comprehension syntax, it still only applies to IEnumerable<T>
. We do have other monads in C# like Task<T>
, but how can we use LINQ on those?
It is possible that many of the assumptions in this question are completele wrong, as I'm still trying to grasp some of the monad magic stuff. Please correct me if I'm wrong :)
LINQ query syntax is merely syntactic sugar, and doesn't know anything about IEnumerable<>
, which is why you can use it for other things.
If you check the C# language specification, it describes how LINQ's query expressions should be transformed in section 7.16.2
The C# language does not specify the execution semantics of query expressions. Rather, query expressions are translated into invocations of methods that adhere to the query expression pattern (§7.16.3). Specifically, query expressions are translated into invocations of methods named Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupBy, and Cast.These methods are expected to have particular signatures and result types, as described in §7.16.3. These methods can be instance methods of the object being queried or extension methods that are external to the object, and they implement the actual execution of the query.
Your specific example is described as
A query expression with a second from clause followed by a select clause
from x1 in e1
from x2 in e2
select v
is translated into
( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => v )
So, using the variable names from your example, any xs
that has a method Treturned SelectMany(Func<Tx,Tys>, Func<Tx,Ty,Treturned>)
can be used in a statement like
Treturned returned =
from x in xs
from y in ys
select r;
This will compile exactly when
Treturned returned = xs.SelectMany(x => ys, (x, y) => r);
does, which is any time such a method exists on xs. The fact that SelectMany
exists for IEnumerable<>
doesn't prevent us from equiping other types with methods or extension methods with the same name.
C# can infer the types for the lambdas from the fact that it knows what xs
is, and from that can look up the types to the arguments of xs
's SelectMany
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With