Ramda: drop array items starting from the tail

Created on 3 Apr 2015  Â·  38Comments  Â·  Source: ramda/ramda

How about adding negative index to drop that returns the array items dropping the ones from the tail.
R.drop(-2,[1,2,3,4,5]) //=> [1,2,3]
Current implementation of drop accepts negative arguments but returns weird result:
R.drop(-2,[1,2,3,4,5]) //=>[,,1,2,3,4,5]

Most helpful comment

So just what does R.take(R.dump) output? :poop: ?

Apologies to non-English speakers, and those with tender ears.

All 38 comments

Well we should definitely fix drop so that it doesn't have the current odd behavior when passed a negative number.

But I'd have to think a little more about the main point. It feels odd. In drop (n), the n does not to my mind represent a _position_, for which a negative number has a fairly obvious meaning by extension. Instead, it represents a _count_. There's simply no clear meaning to switching it to negative. The current semantics of drop easily extend to streams, for instance. But there a negative value would be incoherent.

So my initial reaction is negative. But people have been able to convince me before. :smile:

Which people I need tips :) ?
Joke aside what do you recommend instead for dropping elements starting from the tail, is adding another function better choice? Reversing is too inefficient.

if you know the length of the list you can just slice it down to the desired size.

I'm doing it but its ugly, might as well call array slice directly. On the other hand Ramda drop, head & last add value by dealing with low level stuff so I don't have to.

can you post the code in question?

I have a list of elements [1,2,3,4,5] and a remove button that pushes that last element into removing items where removal animation is played. In this case 5 would be put into removing array while [1,2,3,4] will stay at entries array. If I removed items from the start, head and tail would fit perfectly.

https://github.com/bobiblazeski/ramda-animate/blob/master/client/views/entries.jsx#L15
http://ramda-animate.meteor.com/

When I need to drop several elements at once from the tail I will need something like take and drop that work from the end.

The difficulty is that Ramda's default type is logically the _list_. There is no real list type in JS, so we settle for arrays. And occasionally we'll compromise and include some functions that are specific to this implementation (last, init, and the implementation details of slice.) It's harder to do this with drop, which already has a clear meaning for streams.

R.drop should not accept a negative count. R.slice(0, -2, [1, 2, 3, 4, 5]) works as expected.

Since you already compromise on last and init why not adding dump (discard) as drop from the tail?

Are you suggesting R.dump(2, [1, 2, 3, 4, 5]) would evaluate to [1, 2, 3]? Would there be an accompanying function for taking the last n elements from a list?

So just what does R.take(R.dump) output? :poop: ?

Apologies to non-English speakers, and those with tender ears.

@davidchambers Yes that's correct . I haven't seen a need for mirrored take, though it might be logical, I'm little confused about the order of the returned elements. Maybe its better to see do users need it.

@CrossEye ROFL

is it better to take(dump) or drop(load)? And do we need dumpWhile, e.g. dumpWhile(reading)?

Well ditch & flake should be also considered, though they all sound like bad date, but discard is longer. Maybe leave, that makes sense leave(5) leaves last 5 items

i agree with @davidchambers here, this function is simply expressed as R.slice(0, -2, [1, 2, 3, 4, 5]) and if you want you could define dump = R.slice(0) and there you go

I haven't seen a need for mirrored take, though it might be logical, I'm little confused about the order of the returned elements.

That's my concern. leave alleviates this concern, but in no way suggests that we're "leaving" elements at the end of the list.

I'm opposed to adding these functions, as I think slice is clearer if a few characters longer.

@buzzdecafe You forgot the negative argument. What does dump(-2) mean

@davidchambers where does drop suggests that we are dropping from the front? Leave suggests that we are leaving the tail. IMHO leave should be added if you keep drop & init.

You forgot the negative argument. What does dump(-2) mean

if dump = R.slice(0); then dump(-2, [1,2,3,4,5]); //=> [1,2,3]

http://bit.ly/1HsCorY

[W]here does drop suggests that we are dropping from the front?

Since take and drop accept any sequence — including event streams and infinite sequences — the sequence must be processed in order. One can't take the last event from an event stream or the last value of an infinite sequence.

@buzzdecafe That's what it does, what does it mean to dump (leave) negative number of elements?
@davidchambers OK then why do you have init & last, remove them both? I kinda feel that you judge leave with different weights then functions you already have.

i was just trying to satisfy this: R.drop(-2,[1,2,3,4,5]) //=> [1,2,3] that can effectively be done as R.slice(0), that's all.

Right, if we really wanted to define dump in terms of slice, it would presumably be something more like R.useWith(R.slice(0), R.multiply(-1), R.identity). But the idea is clear.

I just realized that init & last are what I need in my case, so don't remove them. I still think that leave fits ramda quite well but its your call. Have a nice weekend.

OK then why do you have init & last, remove them both?

Haskell provides head, tail, last, and init. Clojure provides first, rest, last, and butlast. We're in good company.

http://www.nizkor.org/features/fallacies/appeal-to-authority.html If I'm not mistaken Haskell & Clojure default structure is single linked list, Ramda is JavaScript library based on arrays. Even if you put those fancy streams & endless structures arrays would be the bread and butter of JavaScript development. Don't forget what you written on the Ramda homepage.

The fact that last and init appear in both Haskell and Clojure (despite being inefficient) suggests that they are useful functions. It would be helpful if you would provide examples of dump and leave from other languages and libraries.

Like this http://www.lispworks.com/documentation/HyperSpec/Body/f_butlas.htm
Clojure seems to have two versions https://clojuredocs.org/clojure.core/butlast https://clojuredocs.org/clojure.core/drop-last
Leave is my proposed name for the function that drops n items starting from the back, since its short and intuitive, but whatever you wish to name it(dump, discard, butlast..), is fine with me as long as its not very long

Remember that what we're trying to do in Ramda is to make available a style of coding familiar in a number of other languages, but not so easy to do currently in Javascript. So it's not so much an appeal to authority as a glance at those systems that inspire us.

It's a point that been hard to make when people want our API to do just what Underscore's does: Underscore is not a primary motivator. It's Haskell, LISP, ML, Clojure, Erlang, perhaps F# that offer models for us. We don't slavishly copy these, but inspiration from them guides us quite a bit.

As to the arrays, Ramda is a library based around _lists_. Because there isn't a built-in list implementation, we settle for (dense) arrays, but we mostly don't deal with them as arrays. Although we mostly think of them as lists, we sometimes think of them as more generic types. (Our map runs on any Functor, our take or filter runs on any stream, etc. It's a subtle point, and certainly not well communicated, but it is important. The fact that our lists are implemented as arrays is very much secondary. If we had a built-in, efficient list implementation, I would have chosen that instead.

Then why do you market it as "Using Ramda should feel much like just using Javascript. It is practical, functional Javascript". Whether you like it or not array & objects are what JavaScript runs on. That's what the ecosystem runs on, and what gadzilion libraries used in any project take and return as arguments. If you want your functional island say it, people will go to underscore, lodash and stop bugging you with dumb feature requests.

This is not at all a dumb request. It's just more controversial than you would have guessed!.

And the reason for that is that it runs into the list/array dichotomy. I have much less concern with a new function than with modifying drop. drop has existing semantics that make sense equally for lists, lazy lists, infinite lists, and streams. While Ramda works well with arrays, it only works well with arrays that are list-like, meaning dense arrays. Bad things might happen if you pass it sparse arrays, something we still need to document better.


Also note that I don't see Ramda particularly in competition with Underscore/lodash. (If nothing else, just compare our download stats! :smile:) Those are great libraries, and they have places that Ramda could never fill, nor would we ever want to. Ramda is designed for a different style of coding, and it's one very different from the core style emphasized in those libraries. (lodash-fp looks to be an attempt to come part of the way along our journey, but from what I can tell, it's mostly at the surface level and doesn't deal with some of the important issues of immutability or API simplicity. I may be wrong about this; at some point I'll give it a thorough examination.)

In essence, I don't see Ramda as really in competition with Underscore or lodash; if people want to go to those libraries, that's fine. I hope Ramda is useful to others; it's already grown far beyond my wildest imaginings, but I'm much, much more interested in making sure that it's the kind of library that I really want to _use_.

@CrossEye I don't want Ramda to compete with underscore & lodash they're fine libraries and I'm using lodash in some of my projects and I'm satisfied with.
However Ramda is my favorite and I try to use it everywhere I can and especially in all my new projects. Its only through using the library to solve my daily problems that I discover what works and what doesn't. That's my understanding of the world practical.
I know fairly well that I have a lot of nasty habits that I need to change to make Ramda magic work and I'm ready to do that. However Ramda must play well within the JavaScript ecosystem, libraries that don't do that such as http://www.shenlanguage.org/ , https://linqjs.codeplex.com/ are nothing more then curiosity and don't have a place in my toolbox.
So I don't take kindly on hearing that library author talking on dropping arrays and base Ramda on lists if they're available. Its fine to draw inspiration from lisp, haskell, ml etc but don't forget that you're promoting Ramda as practical functional JavaScript library. Your words not mine. Practical tools are made to solve problems in the wild, and JavaScript community has different needs then erlang, haskell & clojure.
And I don't care would you add some feature I request, I could always fork /extend Ramda or use something else, its your attitude toward practicality that worries me. I freak out that three of you will put the tinfoil hats, and sacrifice inter operability with the JavaScript ecosystem for some purity that 0.0000000001% of programmers ever want or need.
Anyway so far the experience with Ramda has been fun, I'm successfully using it on both the client and the server, in conjunction with meteor, d3, node, react and really, really enjoying its approach. I only hope it would last.

Personally, I would prefer to see R.drop refactored to return the same unmodified array when a negative index is given, rather than a new mangled array. And pending consensus here, I would be happy with new functions to address this use-case.

To throw some other prior art into the discussion, Scala has takeRight and dropRight methods defined in the IterableLike trait, along with scalaz's IList which has those and also includes takeRightWhile and dropRightWhile.

@scott-christopher: We would definitely consider changing the behavior when a negative value is passed to drop. We try not to go too far down the rabbit hole of error checking user input, preferring to live with _caveat emptor_ or the doctor's "Then don't do that," response to, "Doc, it hurts when I do this." But even though it's somewhat understandable what happens, it's fairly bizarre, and we might want to fix it.

As already mentioned, another function for this that operates on array-like structures would certainly be considered.

@bobiblazeski:

So I don't take kindly on hearing that library author talking on dropping arrays and base Ramda on lists if they're available.

I think you misunderstood. We are not looking at changing our underlying implementation. Ever. However, had the language given us a reasonable choice between an efficient pair-based list structure and and efficient index-based vector structure, we would have unhesitatingly chosen the former as the cleaner, more obvious choice.

We also look to expand where and with what types our functions work. Many of them are dynamically dispatched. So, for instance, one of the key functional programming constructs, the map function, does not _only_ work on lists/arrays. It works on any Functor. Actually, our map function is not called in those cases, as it just defaults to the functor's method. But this means for any conforming implementation, you can call Ramda's function as though you were calling it on an array:

map(square, [1, 2, 3, 4, 5]); //=> [1, 4, 9, 16, 25]
map(square, Maybe.Just(3)); //=> Maybe.Just(9)
map(square, Maybe.Nothing); //=> Maybe.Nothing
map(square, Either.Right(4)); //=> Either.Right(16);
map(square, Either.Left("error message")); //=> Either.Left("error message");

Ramda has the goal of making it easy to use lists (again, implemented as dense arrays) but also of making it straightforward to use the same API for other, related types.

don't forget that you're promoting Ramda as practical functional JavaScript library.

Oh, I never do. :smile: I know that @buzzdecafe would be happier if we'd never included the word "practical" in our mantra, because issues like this arise fairly often. As I said in that philosophy document, "practical" is always in the eyes of the beholder. And it's very easy to conflate "practical" with "what would most help me now". But our goal is a little different. When we started on Ramda there were a number of libraries that we could have chosen to use. Ones like Underscore and lodash were practical libraries, but, although they used functional constructs, not were not really functional ones; they ignored far too many FP concerns to be called functional: immutability, common algorithms across data types, referential transparency. Ones like Oliver Steele's Functional Javascript or Michael Fogus' Lemonad were more functional, dealing better with these concerns. But they were not really practical libraries; they weren't useful for day-to-day coding. What we set out was to create a library that was both practical and functional. But because there were already great practical toolkits available, ours almost by definition has to focus more on how to do FP right. While it needs to remain useful for day-today work, it cannot do so at the expense of the API.

An example recently came up in the Gitter Room:

raine: damn, after ramda i've come to dislike lodash's mutating side effects
raine: some library i was passing a _.merged object was changing the source object
jdalton: you can always _.merge({}, obj, other)
paldepind: Sure. You can always wrap a mutating function. But the problem IMO is that you always needs to be on guard and remember which functions mutate and which don't.

With Ramda you simply know that Ramda functions do not mutate your input data. That sort of consistency is part of what makes the library what it is. It's not purity its own sake; it's consistency so that users always know what to expect. Look at the function in question: drop is documented as returning "a new list containing all but the first n elements of the given list." We could change it in some way to make n a pointer to an index, and describe what happens when that index is negative. Or we could document the function with a big or for the two cases. But neither keeps the simplicity of the API or the understandability of n as a simple cardinal number.

I freak out that three of you will put the tinfoil hats, and sacrifice inter operability with the JavaScript ecosystem

I personally find that a metal colander works much better than tinfoil. But I do think that Chambers looks rather fetching in that little Reynolds Wrap number...

I don't apologize for any lack of interoperability with other JS libraries. These are generally not meant to be used together. But even less do I apologize for an API that differs from related ones in other libraries or in the ES specification. If those tools helped me program in the way I wanted to program, I would not have bothered starting Ramda. I'm more concerned when our API seems out of sync with other functional languages. But even then, we have to make the call for ourselves, based on what's achievable, what's practical, and what's consistent with the rest of the library.

For example, there has been a lot of pressure (for example, #452) to add an index parameter to some of our core functions. There's enough reason for it that we have included such functions as mapIndexed. But we have strongly resisted adding it to map. We cite issues like the classic problem faced by libraries like Underscore that pass such parameters:

_.map(['1.1', '2.2', '3.3'], parseFloat); //=> [1.1, 2.2, 3.3], but
_.map(['1', '2', '3'], parseInt); //=> [1, NaN, NaN]

because such issues are easiest to discuss. But there is a deeper underlying problem. The index parameter is fundamentally _meaningless_ in the more general usage of map. What does index mean for a stream? For a Maybe? Because of these concerns, we have kept the index and object parameters out of such functions. We get criticized on occasion over this for being too idealistic, for failing to live up to our goals of practicality. But I think the criticism here is mostly misguided. We remain practical by being easily useful in day-to-day work, not by happening to include whatever functions meet someone's current need, and definitely not by matching our API to those of other Javascript libraries.


Obviously you've touched a bit of a nerve here, and I didn't mean to take out my frustrations on you. Some of the above was material that I planned on including in my Philosophy of Ramda essay, but in the end didn't bother for the expediency of finally getting the damned thing finished. Some of it is simply a way to gather my thoughts together so that the next time someone throws "practical" in our faces, I can point them to this comment. And some of it is a reaction to the fact that you address these issues in such terms after publishing an article about Ramda that mentioned that you, "have a bitter sweet relationship with its authors, who never gave me what I've asked, but they always delivered what I need." I would hope that this could be another such case!

Soothing words, if you keep Ramda work well with objects and arrays that's enough interoperability I feel it needs. If users have normal JavaScript arrays and objects they could always marshal dirty job to imperative code, and feed the results back to Ramda when they're done, just like if they used plain JavaScript.
Regarding streams & other fancy data structures I've barely use them, but then I barely used compose & currying before Ramda because they were very inconvenient, so I'm looking forward what you'll cook about them, I might find it useful.
I support you 100% behind decision to avoid mutation, that's how I normally code and go back to mutation only when I have to.
I definitely don't want indices into map, but I'm still on the fence about keeping indexed functions, I found that I could remove the need for them and use map instead of mapIndexed, but not on the first try, but until community finds a way to solve problems without the need for indexed they're useful crutch.
Ramda is still very young, this discussion would have never happened if I knew that init is what I need in my use case,and with time and users the right patterns and practices will emerge . @buzzdecafe is wrong world doesn't needs another functional ghetto, Ramda can bring purer functional style to the masses and by the end of this year you might be surprised when you compare its download stats with those of _, the difference might be much closer then you expect.

@buzzdecafe :+1:
OK enough talking from me I have a work to do in finding good examples to promote library with a sheep inspired name and wrong order of arguments.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

luizbills picture luizbills  Â·  43Comments

entropitor picture entropitor  Â·  30Comments

sadasant picture sadasant  Â·  55Comments

esamattis picture esamattis  Â·  40Comments

DaleLJefferson picture DaleLJefferson  Â·  91Comments