elm-community / list-extra

Convenience functions for working with List.

Home Page:http://package.elm-lang.org/packages/elm-community/list-extra/latest

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

groupWhile should return a List of Nonempty, not a List of List

ryan-senn opened this issue · comments

groupWhile : (a -> a -> Bool) -> List a -> List (List a)

Should be

groupWhile : (a -> a -> Bool) -> List a -> List (Nonempty a)

The issue is that Nonempty is a different package obviously. But as it stands it's kind of wrong, no? Maybe it belongs on the other package though.

groupWhile f [] would be empty. So its possible.

But is groupWhile the only function in this package here that would be more accurately typed by replacing some List by a Nonempty? I don't think so. Just going through the package documentation page from top to bottom, I quickly encountered another one: setAt can never return Just [], so its most accurate return type would be Maybe (Nonempty a), not Maybe (List a).

I'm not saying that the type of setAt should be changed. But I find it strange to somewhat arbitrarily pick one function from the package and bring Nonempty in for it. Wouldn't a more meaningful approach be to:

  1. go trough all functions in the package and check for which ones such a change would be possible,
  2. decide whether it's worth it overall.

@jvoigtlaender The return type for setAt recently changed to get rid of the Maybe, so it no longer makes sense to return a Nonempty (the same applies to most/all of the other *At methods). If you pass it an index that is less than 0 or greater than or equal to the list's length, it will not insert the element and is essentially a no-op. Therefore, when given an empty list, it will always return an empty list. Eliminating the methods whose return type recently changed, I don't see any obvious candidates that would benefit from Nonempty.

Okay, fine for *At, but it was really just the first example I came upon. My real question is not about setAt, but about whether, if Nonempty is brought in as a dependency for this package, more thought should go into what that means (or could mean) for the whole set of functions, not just the individual function groupWhile.

@Chadtech groupWhile f [] would still return List NonEmptyList. [] is a list of non-empty lists. (In fact, it's a list of ANYTHING.)

Okay I see now @z5h . Yeah I was wrong.

@pzp1997 made a suggestion of filterEmpties : List (List a) -> List (a, List a) as a compromise. I think that solves my use case and won't break anyone's code. Weather or not that's the best path forward is unclear.

@pzp1997 's idea sounds good to me too.

I think we should change the type signature of groupWhile to a ( a, List a ) non-empty type. But changing the type creates some difficulty. If the type is ( a, List a ) instead of List a then its not compatible with any function thats meant to operate on List a, at least not without a helper function. In my own use of groupWhile I notice the values are List a before and after the use of groupWhile, so I will need to add some kind of helpers converting from and to this non-empty type.

Thats not bad. Helpers arent bad. But it is added code that make things a tiny bit more complicated.

So how about we also add something like @pzp1997 's idea of filterEmpties, or I had in mind just bringing in 3 or 4 non-empty list helper functions into List.Extra. Such as..

mapNonEmpty : (a -> b) -> (a, List a) -> (b, List b)
toNonEmpty : List a -> Maybe (a, List a)
fromNonEmpty : (a, List a) -> List a

Ideally the following should hold true:

myList
    |> List.groupWhile f
    |> List.map List.head -- I'm expecting a list of concretes, not Maybes. Otherwise the group should not even exist to start with!

Not sure about the best way to implement that would be. We obviously can't re-use the current List.head.

I ended up simply copying the existing implementation and modifying it with Nonempty like so for my own needs:

groupWhile : (a -> a -> Bool) -> List a -> List (Nonempty a)
groupWhile eq xs_ =

    case xs_ of
        [] ->
            []

        x :: xs ->
            let
                (ys, zs) =
                    List.Extra.span (eq x) xs
            in
                (List.Nonempty.Nonempty x ys) :: groupWhile eq zs

And now I can use Nonempty.head which correctly returns a concrete, not a Maybe.

It for sure is a tricky question. Maybe groupWhile could be moved to Nonempty?

Well if the proposal to change the return type of groupWhile to ( a, List a ) is accepted, then you would be able to do

myList
    |> List.groupWhile f
    |> List.map Tuple.first

After thinking about it more, I'm beginning to think that this is the right way to go.

Yeah? I still think we should move forward with this.

In the PR, I recommended including helper functions for non-empty lists and then the topic shifted over to how much of a problem with out be to add helper functions and cohere other functions where this non-empty stuff is applicable.

I dont think it will be that drastic of a change. As far as helpers go, we already have uncons, which goes from a (a, List a) -> List. I think cons : List a -> Maybe (a, List a) and mapNonEmpty : (a -> b) -> (a, List a) -> (b, List b) would be good to include, but not necessary.

So my expectation (but maybe we should look more closely into this) is to make this change to using non-emptys, we just need maybe two helpers, and we have to make major changes to the other group while functions.

That all seems worth it to me but Im open to different evaluations.

I am in support of moving forward with the PR to make the group functions return (a, List a).

I am still not sure about if the non-empty helper functions should (a) be in List-Extra, (b) be in a different package or module, or (c) not exist in an elm-community package altogether. If we are only planning on adding mapNonEmpty then I think putting it in List-Extra is fine. But if there are many other functions that we want non-empty variants for, a separate package might be best. Maybe someone could compile a list of candidate functions for non-empty variants so that we can make a more informed decision. I don't think this should block merging the primary PR though.

P.S. Small correction, the type of uncons is List a -> Maybe (a, List a). I don't believe there is a dedicated for turning a non-empty list into a list but it is simple to write.

Okay, then how about we just change the type signature for groupWhile to deal with non empties (a, List a) and not include helpers, and just see what happens. I dont think theres anything wrong with just letting folks be helper-less until the topic gets raised again later. If that sounds good I can just merge that PR thats currently open.

Then we can also open up one or more issues to talk about helpers and how non-empty fits into List.Extra

I merged the PR for this, and opened up a discussion at issue #105 . Since this is over, I am closing this issue! 🎉