Open stil4m opened 7 years ago
I think that having two distinct functions doing literally the same but named after two synonyms (member and contains) could be a little bit confusing.
Maybe all we need is just a new function to suit your original problem:
{-| Creates a list composed of elements common to both provided lists.
-}
intersection : List a -> List a -> List a
It would not hurt if there was also an:
intersectionBy: ((a, a) -> Bool) -> List a -> List a -> List a`
Is there still a want/need for this? I could write this :).
I think it makes sense for intersection
/intersectionBy
to exist.
No opinion on if intersectionBy
should be ((a, a) -> Bool) -> List a -> List a -> List a
, or if it should match the other "by" functions: (a -> comparable) -> List a -> List a -> List a
I would vote for:
intersectionBy: (a -> a -> Bool) -> List a -> List a -> List a
is better than:
intersectionBy: ((a, a) -> Bool) -> List a -> List a -> List a
The latter seems unfriendly to function composition.
What would (a -> comparable)
mean? I think, we either include an element or exclude it.
Maybe better name for:
intersectionBy
would be an intersectionWith
?
I am looking at the RamdaJS http://ramdajs.com/docs/ library. All that functions and their names were carefully though out, in the past there were many incompatible changes adjusting to new functions and as a result of discussions on a mailing list, so maybe we could use their achievements?
P.S.
There are other useful list functions there, maybe list-extra could implement some of them as well, like difference
and differenceWith
?
Actually I think it should be
(a -> b -> Bool) -> List a -> List b -> List a
, because there is no need for both lists to be of same type (but they still can).
I like intersectBy
, but one problem I think with the name, is that it doesnt have anything to do with intersections any more. @witoldsz is right in pointing out that they two lists dont need to be the same type, but then, if they arent the same type, then they are necessarily not the same, and there cant be any intersection between them. Even if they are the same type, if you are using a a -> a -> Bool
to determine if they will be present in the output List a
, it doesnt follow that it will be on the basis of them being the same.
It seems like we've abstracted from @stil4m 's original use case (which I think has been fruitful). I've been in his exact use case, and I made myself a helper function called contains
typed List a -> List a -> Bool
.
What about this proposal?
intersection : List a -> List a -> List a
intersection [ 1, 2 ] [ 2, 3 ] == [ 2 ]
doIntersect : List a -> List a -> Bool
doIntersect list0 list1 =
not <| List.isEmpty (intersection list0 list)
doIntersect [ 1, 2 ] [ 2, 3 ] == True
Keep in mind that there already is a function for getting the intersection of two sets, namely Set.intersect
. Does it make sense to duplicate this functionality for lists? In my opinion, it would just encourage people to use the wrong type of collection.
Another question is how would one even define intersection for lists. In terms of handling duplicate values, should intersection [ 1, 1 ] [ 1, 1 ]
equal [ 1 ]
or [ 1, 1 ]
? Should a particular order for the return list be guaranteed, for example intersection [ 1, 2 ] [ 2, 1 ]
could equal either [ 1, 2 ]
or [ 2, 1 ]
?
I am tempted to disagree with what you said about sets. I would think that one might want to preserve the order of a list, and get it's intersection with another list. But then that does force your question about what order the output would have. The order has to be based on either the first or second parameter. Since its based on one or the other, its more like a filter that uses one list on the other; not a true intersection.
So anyway, good points, I agree. That brings us back to the original problem, which I am not sure we've fully formed.
I would think that one might want to preserve the order of a list, and get it's intersection with another list
@Chadtech That is exactly what the original ticket was for.
Right. I'm with ya.
I still feel good about my proposal, if we just change the names (since they arent true intersections). How about only
and hasOnly
? So..
only: List a -> List a -> List a
only list0 list1 =
List.filter ((flip List.member) list1) list0
hasOnly : List a -> List a -> Bool
hasOnly list0 list1 =
not <| List.isEmpty (only list0 list)
@pzp1997
Another question is how would one even define intersection for lists. In terms of handling duplicate values, should intersection [ 1, 1 ] [ 1, 1 ] equal [ 1 ] or [ 1, 1 ]?
Valid point! Intersection combines lists into a set. The function could be implemented, but it is out of scope here, as this is not what OP wanted.
About the only
function… It takes list0
, filters it leaving members of list1
. Am I correct? If so, list0
should be the last argument, it's not a coincidence it's last argument of List.filter
. So, it's basically a special variation on filter
, so maybe the name should be something like filterMembers
, filterEquals
or the like? What do you think?
My proposal is would be:
filterMembers members list =
List.filter ((flip List.member) members) list
or maybe:
filterMembers members list =
list |>
List.filter (\item -> List.member item members)
About hasOnly
: is there a way to stop at first not equal item?
@Chadtech I like your proposal. My only
thought (pun intended) is to switch the order of the lists passed to only
to make partial application and composition easier. I think it is more common that you would have a bunch of lists that you want to limit to the same subset of values than that you want to limit a single list to multiple subsets of values (not sure if that makes sense in words, feel free to ask me for an example if it is not clear). Here's my proposed version of only
.
only : List a -> List a -> List a
only list =
List.filter <| (flip List.member) list
Regarding hasOnly
, I am not sure if I correctly understand the purpose of the function. I was under the impression that it would determine if a list contains only a given subset of values and no other values. If that is the case, then I think your implementation might be slightly flawed. For example, hasOnlyChad [1, 3] [3] == True
, yet the values in the first list are clearly not a subset of the values in the second list. Your implementation seems to determine if the first list contains any values from the second list. This also seems useful in some cases and could be what you intended, but I would say that hasOnly
is not the most intuitive name for this behavior. Instead, I propose the following implementation for hasOnly
.
hasOnly : List a -> List a -> Bool
hasOnly list =
List.foldl (\x acc -> acc && List.member x list) True
EDIT
Just read @witoldsz's comment and he seems to agree with me about the order of the arguments to only
.
Parameter order
Okay I see what you both mean about the parameter order. Yeah, it should be flipped.
name
How about filterMember
, in the singular? "Members" threw me for a loop, because it sounds like its referring to the members of the list. But if we refer to the function member
it makes sense, because thats the basis of the fil. That said member
is sa bad name imo, but I would rather be consistent with it.
hasOnly
In response to @witoldsz, Yeah I am sure there is a more efficient implementation.
Anyway @pzp1997, the purpose I had in mind was just to say if a list has any of the members from a different list. So, member
tells you if the list has a certain element. hasOnly
would tell if you it has any member from a given list. Maybe it needs a better name. Since we are leaning towards referencing member
, a name like anyMember
would work.
@pzp1997
only : List a -> List a -> List a only list = List.filter <| (flip List.member) list
I would suggest renaming the list
to members
, because it represents a list of members we are going to check against, the list
to filter is to be provided.
@Chadtech
How about filterMember, in the singular?
I used plural, because we are checking against a list of members, not a single member.
@Chadtech if I now understand anyMember
correctly, it sounds it like it should be implemented as
anyMember list =
List.any <| (flip List.member) list
This also made me realize that my version of hasOnly
a.k.a. allMember
should be implemented like this.
allMember list =
List.all <| (flip List.member) list
And given the likely implementation of filterMember
, it looks like we've got a pattern here.
I'm beginning to think that maybe the right idea is what was suggested in the original proposal. If we create an alias for flip List.member
then people will be able to easily create predicates for all these functions and more. If we instead choose to make Member
variants for filter
, any
, and all
, what would be stopping us from doing the same for List.partition
, List.Extra.takeWhile
, List.Extra.dropWhile
, List.Extra.filterNot
, etc. for which there exist convincing cases for inclusion as well?
Sorry to turn around and change my opinion so suddenly. I have been thinking about this for a while, yet only realized this today. My "vote" is in favor of adding a function that aliases flip List.member
perhaps called memberOf
. I think that the name is different enough from List.member
that people won't get confused. As a trivial example, this would look like
List.filter (List.Extra.memberOf [1, 4, 9, 16]) [4, 2, 1, 9, 9, 3] == [4,1,9,9]
Often I'll find myself doing
someList |> List.filter (flip List.member myList)
. I assume that other people have this same construct to filter a list.Would it be nice to have a function in this package that makes this code cleaner. Such as:
Then the resulting code would be nicer and more concise:
I can make a PR on this if my suggestion some kind of approval.