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<TResult> Join<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer,
IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> 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 itself, is that the proper way to do it with regards to laziness and expression trees that will be used under the cover? Will the seq
type annotations break anything in that respect? Those were necessary to actually allow for using seq
and list
values as inputs.
(This is of course supposed to become an extension for the Seq
module; I just haven't bothered to do that yet.)
join
function and re-order the params to better support pipelines? That's sort of what you've done, but the query expression seems unnecessary. \$\endgroup\$