bradgonesurfing
bradgonesurfing

Reputation: 32182

Cross Type LINQ

I have the following code

    [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
    public void TestEnumOfMaybe()
    {
        List<Maybe<int>> l = new List<Maybe<int>>();
        l.Add(1.ToMaybe());
        l.Add(Maybe.None<int>());
        l.Add(3.ToMaybe());

        var y = from x in l
                from y in x
                select y;

    }

My Maybe type accepts all the Select and SelectMany methods to make it Linq compatible and I have tests to prove it so. However I can't seem to figure out a way to do cross type LINQ composition as in the above test case. The error I get is

expression of type 'FunctionalExtensions.Maybe<int>' is not allowed in
a subsequent from clause in a query expression with source type
'System.Collections.Generic.List<FunctionalExtensions.Maybe<int>>'.  

Type inference failed in the call to 'SelectMany'.
FunctionalExtensions*

Is there a way to combine the two LINQ types or am I out of luck here? The complete Maybe.cs implementation is at

https://gist.github.com/4016243

Upvotes: 3

Views: 414

Answers (4)

bradgonesurfing
bradgonesurfing

Reputation: 32182

For my specific problem it turns out that having Maybe implement IEnumerable is the way to go and it make supporting IOBservable trivial as well. My two test cases which now pass.

    [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
    public void TestEnumOfMaybe()
    {
        List<Maybe<int>> l = new List<Maybe<int>>();
        l.Add(1.ToMaybe());
        l.Add(Maybe.None<int>());
        l.Add(3.ToMaybe());

        var k = from q in l
                from y in q
                select y;

        k.Should().BeEquivalentTo(new[] { 1, 3 });

    }

    [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
    public void TestObservableOfMaybe()
    {
        List<Maybe<int>> l = new List<Maybe<int>>();
        l.Add(1.ToMaybe());
        l.Add(Maybe.None<int>());
        l.Add(3.ToMaybe());

        var o = l.ToObservable();

        var k = from q in o
                from y in q
                select y;

        var m = k.ToEnumerable();

        m.Should().BeEquivalentTo(new[] { 1, 3 });

    }

In general I think the LINQ system forbids chaining different types together even if the SelectMany signatures will line up. I'm yet to be convinced otherwise.

Upvotes: 0

Marc Gravell
Marc Gravell

Reputation: 1062855

As Jon notes, the LINQ translation of from,from to SelectMany is:

var query = l.SelectMany(x => x, (x, y) => y);

The first thing to note is that your SelectMany is defined against an individual Maybe<T>, so for this to make sense, l must be a Maybe<T> (for some T), not a list. The next thing to note is that the signature has to match. For example:

public static Maybe<TResult> SelectMany<TSource, TMaybe, TResult>(
    this Maybe<TSource> m, Func<Maybe<TSource>, Maybe<TMaybe>> f,
    Func<Maybe<TSource>, Maybe<TMaybe>, TResult> g)
{
    throw new NotImplementedException();
    // return m.Bind(x => f(x).Bind(y => g(x, y).ToMaybe()));
}

And:

var obj = l[0];

Now this works (explicit generics):

var q = obj.SelectMany<int, int, Maybe<int>>((x => x), (x, y) => y);

which is identical to:

var r = obj.SelectMany((x => x), (x, y) => y);

which is identical to:

var query = from x in obj
            from y in x
            select y;

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500595

The translation of this:

// Changed variable from y to query for clarity
var query = from x in l
            from y in x
            select y;

is simply:

var query = l.SelectMany(x => x, (x, y) => y);

Note that nothing is being called on y here.

Now l is a List<Maybe<int>>, so you need to try to find an appropriate SelectMany method which would be valid here. Unless you've added any more, it will look in Enumerable.SelectMany, and every overload there requires the the first delegate to return an IEnumerable<T> for some T.

So that's why it's not working. You could make it work by making Maybe<T> implement IEnumerable<T> (presumably either yielding a single result or no results). It's hard to say for sure whether that's what you're aiming for, but you've basically got to get that expansion to work.

Alternatively, you could write a new overload for SelectMany targeting IEnumerable<T>, for cases where the result is a Maybe<T> rather than an IEnumerable<T>. That would be pretty unintuitive though, IMO.

Upvotes: 6

Thomas Levesque
Thomas Levesque

Reputation: 292435

Your SelectMany method takes a Maybe<TSource> as a parameter, but you're using it on a List<Maybe<TSource>>, so it actually uses the SelectMany method from Enumerable. It's not clear from your example what you expect the type of y to be, but as it is the code doesn't really make sense.

Upvotes: 1

Related Questions