HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Join() equivalent function for F# sequences

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
equivalentfunctionjoinforsequences

Problem

I am porting C# code to F# that makes use of LINQ's Join() extension method. Just as I use that in method call chains, I would of course like to have an F# function to pipe into. However, there is no equivalent in the Seq module, and while the same result could also be achieved with a nested lambda using Seq.where, I'd also want something that tells me by name that it is in fact a join.

I came up with this function using a simple query expression:

let seqJoin innerKeySelector outerKeySelector resultSelector (innerSequence : 'b seq) (outerSequence : 'a seq) =
    query {
            for outer in outerSequence do
            join inner in innerSequence on
                (outerKeySelector outer = innerKeySelector inner)
            select (resultSelector outer inner)
    }


While this works, I'm not sure about the idiomaticity of the API.

LINQ's Join() method has this signature:

public static IEnumerable Join(
this IEnumerable outer,
IEnumerable inner,
Func outerKeySelector,
Func innerKeySelector,
Func resultSelector)


Parameters are 'logically' ordered by importance, the first being the sequence on which the method is (syntactically) called. In F#, in order to be able to pipe the 'original' sequence into the function, that argument needs to be last instead of first, which requires shuffling the whole signature around a bit.

In order to keep the two sequences next to each other, I moved innerSequence to the second to last position, and to stay in line with their order, I also switched outerKeySelector and innerKeySelector. I wonder, though, whether once I've changed it that far, it might be more idiomatic to go all the way and make it the full reverse of the "C# version", in part because now the resultSelector parameter separates the sequences from their key selector functions and looks a bit out of place in that position. What would be the most 'natural'/idiomatic way to arrange those parameters?

As for the query

Solution

I am porting C# code to F# that makes use of LINQ's Join() extension method. Just as I use that in method call chains, I would of course like to have an F# function to pipe into.

I think that using Join() directly in C# is actually not that common, since query syntax is much nicer for that. And I think you should consider the same thing in F#: you already know about query expressions and join, so why not use that?

Though I understand that function call chains are much more flexible, so this might not be a good option for you.

Have you considered removing the resultSelector altogether? Tuples are not very idiomatic in C#, but they are in F#, so you might want to use them.

Since you want to copy the behavior of Enumerable.Join(), I would use that to implement your method.

When considering the order of parameters, I think you should think about what is the most natural usage. I think it's this:

outer
|> Seq.join inner (fun o -> o.OKey) (fun i -> i.IKey)


This will make the order of parameters of the function quite weird, but I think it makes the most sense this way.

Putting this all together:

let join (innerSequence : 'b seq) outerKeySelector innerKeySelector (outerSequence : 'a seq) =
    outerSequence.Join(innerSequence,
        Func(outerKeySelector), Func(innerKeySelector),
        fun outer inner -> (outer, inner))


(Those Func casts are necessary, I think it's because F#'s weird rules for implicit casting from lambdas to delegates.)

Code Snippets

outer
|> Seq.join inner (fun o -> o.OKey) (fun i -> i.IKey)
let join (innerSequence : 'b seq) outerKeySelector innerKeySelector (outerSequence : 'a seq) =
    outerSequence.Join(innerSequence,
        Func<_, _>(outerKeySelector), Func<_, _>(innerKeySelector),
        fun outer inner -> (outer, inner))

Context

StackExchange Code Review Q#60928, answer score: 2

Revisions (0)

No revisions yet.