Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Monadic Programming in C#

In Haskell, we have the filterM function. The source code for it is:

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do
flg <- p x
ys  <- filterM p xs
return (if flg then x:ys else ys)

Translating from do notation:

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  p x >>= \flg -> 
                    filterM p xs >>= \ys -> 
                    return(if flg then x:ys else ys)

To the best of my understanding, >>= on lists in Haskell and SelectMany on IEnumerablein C# are the same operation and so, this code should work just fine:

    public static IEnumerable<IEnumerable<A>> WhereM<A>(this IEnumerable<A> list, Func<A, IEnumerable<bool>> predicate)
    {
        // Like Haskells null
        if (list.Null())
        {
            return new List<List<A>> {new List<A>()};
        }
        else
        {
            var x = list.First();
            var xs = list.Tail(); // Like Haskells tail

            return new List<IEnumerable<A>>
                {
                    predicate(x).SelectMany(flg => xs.WhereM(predicate).SelectMany(ys =>
                        {
                            if (flg)
                            {
                                return (new List<A> {x}).Concat(ys);
                            }
                            else
                            {
                                return ys;
                            }
                        }))
                };
        }
    }

But it doesn't work. Can anyone point me to what's wrong here?

like image 862
Michael Avatar asked Apr 27 '13 16:04

Michael


2 Answers

My C# is a bit rusty, but it looks like your base case is wrong. You're returning the equivalent of [] (an empty list) while the Haskell version returns [[]] (a list containing an empty list).

Your recursive case has the same problem. For example, in the else branch the Haskell version returns [ys] while your version returns ys. Remember that return in the list monad makes a single element list and has nothing to do with the return keyword in C#.

like image 107
hammar Avatar answered Sep 29 '22 13:09

hammar


It looks like your C# code is equivalent to:

filterM          :: (a -> [Bool]) -> [a] -> [[a]]
filterM _ []     =  return []
filterM p (x:xs) = 
  return $
    p x >>= \flg -> 
    filterM p xs >>= \ys -> 
    if flg then x:ys else ys

I.e. return is in the wrong spot.

I would expect something like this:

        return predicate(x).SelectMany(flg => 
            xs.WhereM(predicate).SelectMany(ys =>
                new List<IEnumerable<A>> { flg ? (new List<A> {x}).Concat(ys) : ys }))
like image 33
Sjoerd Visscher Avatar answered Sep 29 '22 11:09

Sjoerd Visscher