New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List.append has flipped argument order #776

Open
rtfeldman opened this Issue Dec 5, 2016 · 7 comments

Comments

Projects
None yet
7 participants
@rtfeldman
Member

rtfeldman commented Dec 5, 2016

tl;dr List.append should be renamed List.appendTo, to reflect the actual order of its arguments.

The Bug

Here is some Elm code:

[ "foo", "bar" ]
    |> List.append [ "baz" ] 

Its output, surprisingly, is:

[ "baz", "foo", "bar" ]

Clearly this function does not follow Elm's standard for argument ordering!

The History

I'm assuming this was originally implemented according to how append is implemented in Haskell: the contents of second argument are appended to the contents of the first. It's probably been this way since day one of Elm, back before standardized argument ordering or (|>) existed.

The trouble is that now we have a strong convention in Elm that says if you write the code in the original sample above, you should reasonably expect to get [ "foo", "bar", "baz" ].

One might argue "yeah but then List.append [ "foo", "bar" ] [ "baz" ] gets you [ "baz", "foo", "bar" ] which is surprising in a different way. This is true, but in practice nobody ever fully applies List.append except in examples. We write [ "foo", "bar" ] ++ [ "baz" ] instead.

The place where you want to call List.append in actual Elm code is in a pipeline, but I never ever ever write |> List.append foo because that code will do the opposite of what it says it's going to do. (I always write |> (++) foo instead, because it's not self-documenting at all - which unfortunately is an improvement over misleading.)

Proposed Fix

I hope it's not controversial to conclude that List.append shouldn't behave in a surprising way under the circumstance in which people will most often reach for it.

The naive fix for this would be to flip it. Unfortunately this would be a disastrously backwards-incompatible change, as its type signature is List a -> List a -> List a - meaning everyone's code will still compile for the flipped version, it will just do something completely different. This does not seem like a viable choice for a core function.

A better fix, in my opinion, is to rename it (and any other core append functions implemented this way) to prepend appendTo, per @rgrempel's suggestion. Here's how that would look:

[ "bar", "baz" ]
    |> List.appendTo [ "foo" ] 
[ "foo", "bar", "baz" ]

If that change happens, then:

  1. The surprising List.append function is replaced with an identically-implemented List.appendTo, which follows standardized Elm argument order and works as expected in a pipeline.
  2. List still exposes a function that makes it Monoidal, in case that ever matters.
  3. Some time in the future, enough major versions of core will have been released (presumably with auto-migrations in between) that a List.append can be safely introduced again, this time with Elm-style argument order. (This made sense with List.prepend but would not be necessary with List.appendTo.)
@process-bot

This comment has been minimized.

Show comment
Hide comment
@process-bot

process-bot Dec 5, 2016

Thanks for the issue! Make sure it satisfies this checklist. My human colleagues will appreciate it!

Here is what to expect next, and if anyone wants to comment, keep these things in mind.

process-bot commented Dec 5, 2016

Thanks for the issue! Make sure it satisfies this checklist. My human colleagues will appreciate it!

Here is what to expect next, and if anyone wants to comment, keep these things in mind.

@rgrempel

This comment has been minimized.

Show comment
Hide comment
@rgrempel

rgrempel Dec 5, 2016

Contributor

One way of looking at this problem is that the word append does not carry quite enough meaning to clearly tell you which argument will be appended to which argument.

That is, given append : List a -> List a -> List a, is there anything about the word "append" that tells us whether the first argument will be appended to the second, or the second to the first? I suppose you might argue that there is potentially an intuition that the order of the results will match the order of the arguments, but I'm not really sure that is so -- that is, I don't think there is much of a natural intuition one way or the other.

For what append currently does, I wonder whether appendTo would be a better name. For instance, if I saw:

[ "foo", "bar" ]
    |> List.appendTo [ "baz" ] 

... then I think I would pretty unambiguously assume that I was going to end up with [ "baz", "foo", "bar" ].

What about the non-pipelined form, i.e.:

List.appendTo [ "baz" ] [ "foo", "bar" ]

I think that [ "baz", "foo", "bar" ]would also be non-surprising in that case, though I'm not quite as sure that others would agree.

I suppose the question this raises is whether appendTo creates a stronger intuition about argument ordering than prepend does.

The other angle on this that would be worth considering is this. Leaving aside the proper name for the function for the moment, the prior question is whether the first argument ought to end up first in the final list, or second in the final list. It seems to me that this depends on whether you think the thing being carried along in the pipeline is more likely to end up first or last in the final list.

Now, typically (but not always) the thing being carried along in the pipeline (i.e. the second argument) is something you've computed, whereas the first argument is often a constant. (In your case, that is, the ["foo", "bar"] is often a result of a computation carried out in the pipeline, whereas ["baz"] is often a constant).

So, the question reduces itself to: are you more often appending a constant list to a computed value, or a computed value to a constant list?

I don't think I know the answer to that question -- it just seems like it might be a relevant angle on this.

Contributor

rgrempel commented Dec 5, 2016

One way of looking at this problem is that the word append does not carry quite enough meaning to clearly tell you which argument will be appended to which argument.

That is, given append : List a -> List a -> List a, is there anything about the word "append" that tells us whether the first argument will be appended to the second, or the second to the first? I suppose you might argue that there is potentially an intuition that the order of the results will match the order of the arguments, but I'm not really sure that is so -- that is, I don't think there is much of a natural intuition one way or the other.

For what append currently does, I wonder whether appendTo would be a better name. For instance, if I saw:

[ "foo", "bar" ]
    |> List.appendTo [ "baz" ] 

... then I think I would pretty unambiguously assume that I was going to end up with [ "baz", "foo", "bar" ].

What about the non-pipelined form, i.e.:

List.appendTo [ "baz" ] [ "foo", "bar" ]

I think that [ "baz", "foo", "bar" ]would also be non-surprising in that case, though I'm not quite as sure that others would agree.

I suppose the question this raises is whether appendTo creates a stronger intuition about argument ordering than prepend does.

The other angle on this that would be worth considering is this. Leaving aside the proper name for the function for the moment, the prior question is whether the first argument ought to end up first in the final list, or second in the final list. It seems to me that this depends on whether you think the thing being carried along in the pipeline is more likely to end up first or last in the final list.

Now, typically (but not always) the thing being carried along in the pipeline (i.e. the second argument) is something you've computed, whereas the first argument is often a constant. (In your case, that is, the ["foo", "bar"] is often a result of a computation carried out in the pipeline, whereas ["baz"] is often a constant).

So, the question reduces itself to: are you more often appending a constant list to a computed value, or a computed value to a constant list?

I don't think I know the answer to that question -- it just seems like it might be a relevant angle on this.

@rtfeldman rtfeldman changed the title from List.append is implemented as List.prepend to List.append has flipped argument order Dec 5, 2016

@rtfeldman

This comment has been minimized.

Show comment
Hide comment
@rtfeldman

rtfeldman Dec 5, 2016

Member

I've wanted both "prepend" and "append" behavior at different times, to be honest.

I really like the appendTo idea! It's clearer to me than either status quo or prepend, and has better long-term characteristics because it eliminates the desire to reintroduce append someday.

I've edited the OP to reflect my view that this is a better solution.

Member

rtfeldman commented Dec 5, 2016

I've wanted both "prepend" and "append" behavior at different times, to be honest.

I really like the appendTo idea! It's clearer to me than either status quo or prepend, and has better long-term characteristics because it eliminates the desire to reintroduce append someday.

I've edited the OP to reflect my view that this is a better solution.

@kuzux

This comment has been minimized.

Show comment
Hide comment
@kuzux

kuzux Dec 27, 2016

append should really be nothing but a synonym for (++). Calling this behaviour append is just downright weird.

kuzux commented Dec 27, 2016

append should really be nothing but a synonym for (++). Calling this behaviour append is just downright weird.

@rtfeldman

This comment has been minimized.

Show comment
Hide comment
@rtfeldman

rtfeldman Dec 27, 2016

Member

@kuzux what strikes you as weird about it?

Member

rtfeldman commented Dec 27, 2016

@kuzux what strikes you as weird about it?

@mgold

This comment has been minimized.

Show comment
Hide comment
@mgold

mgold Dec 27, 2016

Contributor

I agree with @kuzux that if we have append it should be a synonym for (++), but this proposal is that we shouldn't have append. It's much less weird, IMHO, for appendTo to not be a synonym for (++). I support the revised proposal of renaming the function without changing the argument order.

Contributor

mgold commented Dec 27, 2016

I agree with @kuzux that if we have append it should be a synonym for (++), but this proposal is that we shouldn't have append. It's much less weird, IMHO, for appendTo to not be a synonym for (++). I support the revised proposal of renaming the function without changing the argument order.

@Mouvedia

This comment has been minimized.

Show comment
Hide comment
@Mouvedia

Mouvedia Jan 1, 2017

Id support the couple prepend / append.

Mouvedia commented Jan 1, 2017

Id support the couple prepend / append.

@evancz evancz added the problem label Jul 16, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment