patternMinor
Join() equivalent function for F# sequences
Viewed 0 times
equivalentfunctionjoinforsequences
Problem
I am porting C# code to F# that makes use of LINQ's
I came up with this function using a simple query expression:
While this works, I'm not sure about the idiomaticity of the API.
LINQ's
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
As for the query
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
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
Since you want to copy the behavior of
When considering the order of parameters, I think you should think about what is the most natural usage. I think it's this:
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:
(Those
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.