Bug 10959 – std.algorithm.remove is highly bug-prone

Status
NEW
Severity
normal
Priority
P3
Component
phobos
Product
D
Version
D2
Platform
All
OS
All
Creation time
2013-09-03T14:47:27Z
Last change time
2024-12-01T16:18:44Z
Assigned to
No Owner
Creator
bearophile_hugs
Moved to GitHub: phobos#10001 →

Comments

Comment #0 by bearophile_hugs — 2013-09-03T14:47:27Z
This is not an enhancement request, I consider this a bug report. Often when I use std.algorithm.remove in my code I introduce bugs. So I believe std.algorithm.remove is too much bug-prone, some examples: import std.algorithm: remove; import std.stdio: writeln; void main() { auto A = [1, 2, 3]; A.remove(2); writeln(A); // prints: [1, 2, 3] A.remove!q{a = 2}; writeln(A); // prints: [1, 2, 3] A.remove!q{a == 2}; writeln(A); // prints: [1, 3, 3] A = [1, 2, 3]; A = A.remove!q{a == 2}; writeln(A); // prints: [1, 3] (correct) } So I suggest to rename std.algorithm.remove as "removeAtIndex" or something similar. And then to introduce a function remove that removes the given item. But this is not enough, because even this syntax is bug-prone to remove the item '2' from the array 'A': A = A.remove(2); What I should write is just: A.remove(2); This is how it's done in Python, and it's not bug-prone, this is how in my opinion it should be designed: >>> A = [1, 2, 3] >>> A.remove(2) >>> A [1, 3] I don't care about all the discussions about containers, ranges, etc. Currently std.algorithm.remove is a landmine and in my opinion it's not acceptable in Phobos. See also a thread by Manu and friends, that have had problems to remove an array item: http://forum.dlang.org/thread/[email protected]
Comment #1 by tcdknutson — 2013-09-03T14:52:30Z
+1, regardless of impact on backwards compatibility. It's a landmine, and it's bit me a few times in the past too.
Comment #2 by daniel350 — 2013-09-09T17:15:16Z
-1, I disagree on all points except to rename to `std.algorithm.removeAt` and add a complimentary method which instead removes by value.
Comment #3 by bearophile_hugs — 2013-09-10T05:59:32Z
(In reply to comment #2) > -1, I disagree on all points except to rename to `std.algorithm.removeAt` and > add a complimentary method which instead removes by value. Are you saying that std.algorithm.remove is not very bug-prone? And why?
Comment #4 by daniel350 — 2013-09-10T07:28:02Z
After 24 hours of thinking about it, I've come to agree with your statement. My original sentiment was that of likening std.algorithm.remove to its look-alike http://www.cplusplus.com/reference/algorithm/remove. I also saw the slice level purity of the operation as being an attractive quality, however, given the majority of the range interfaces in D are mutating by default, I see no reason why the behavior of this function should be different. To which end, I now agree on all points. Sorry, I'll hope you forgive me. +1.
Comment #5 by bearophile_hugs — 2013-09-10T07:34:20Z
(In reply to comment #4) > Sorry, I'll hope you forgive me. Thank you, but you don't need to ask for forgiveness for just disagreeing with me :-) Disagreeing is a natural part of discussions.
Comment #6 by bearophile_hugs — 2014-01-09T03:11:59Z
In Python to remove an item from an array (list) you use: >>> items = [10, 20, 30, 20] >>> items.remove(20) >>> items [10, 30, 20] With D+Phobos you need: void main() { import std.stdio, std.algorithm; auto items = [10, 20, 30, 20]; items = items.remove(items.countUntil(20)); items.writeln; } So this API is quite bad all around, it's not just bug-prone. So I suggest to introduce a differently named function that works similarly to the current remove (but it doesn't need the bug-prone re-assignment on the left): items.removeAtIndex(1); And modify remove() to work like this, as the Python remove method: items.remove(20);
Comment #7 by damianday — 2014-01-09T11:21:19Z
I absolutely agree, I find this crops up all the time when using containers, so I usually roll my own, which is a shame! Bring on removeAtIndex!
Comment #8 by bearophile_hugs — 2014-01-22T15:33:13Z
> items = items.remove(items.countUntil(needle)); monarch_dodra comments: > Hum... that requires iterating the range twice for a non-RA > range. And you forgot a save: > > items = items.remove(items.save.countUntil(needle));
Comment #9 by daniel350 — 2014-01-22T21:33:11Z
I've just given up on this idiom, instead using: ``` import std.algorithm; import std.stdio; void main() { auto items = [10, 20, 30]; auto t = items.filter!(x => x != 20).copy(items); items = items[0 .. $ - t.length]; writeln(items); } ``` Its the only option that has clear time and space semantics. Ref: http://dpaste.dzfl.pl/84273326
Comment #10 by monarchdodra — 2014-01-22T23:22:14Z
(In reply to comment #9) > I've just given up on this idiom, instead using: > > ``` > import std.algorithm; > import std.stdio; > > void main() { > auto items = [10, 20, 30]; > auto t = items.filter!(x => x != 20).copy(items); > items = items[0 .. $ - t.length]; > > writeln(items); > } > ``` > > Its the only option that has clear time and space semantics. > > Ref: http://dpaste.dzfl.pl/84273326 How is that any different from: items = items.remove!(x => x == 20)() ?
Comment #11 by advmail — 2014-01-23T00:55:42Z
+1 for this bug. I've to double-check remove every single time I use it.
Comment #12 by daniel350 — 2014-01-23T01:52:09Z
(In reply to comment #10) > (In reply to comment #9) > > I've just given up on this idiom, instead using: > > > > ``` > > import std.algorithm; > > import std.stdio; > > > > void main() { > > auto items = [10, 20, 30]; > > auto t = items.filter!(x => x != 20).copy(items); > > items = items[0 .. $ - t.length]; > > > > writeln(items); > > } > > ``` > > > > Its the only option that has clear time and space semantics. > > > > Ref: http://dpaste.dzfl.pl/84273326 > > How is that any different from: > items = items.remove!(x => x == 20)() > ? A fair point, I'd forgotten about the predicate remove.
Comment #13 by bearophile_hugs — 2014-03-16T08:31:34Z
An extra feature of the new "removeIndex" (or "removeAtIndex") function is to optionally accept a range of indexes: myArray = [.....]; myArray.removeIndex(2); myArray.removeIndex(iota(1, 20, 3));
Comment #14 by greensunny12 — 2018-02-10T22:09:30Z
A PR to fix the landmines - https://github.com/dlang/phobos/pull/6154 It's probably too late to rename `remove` :/
Comment #15 by mingwu — 2024-08-11T06:01:02Z
Hit this one today, Has `removeAt` or `removeAtIndex` be added to the std lib? BTW, for associative array, `remove()` is in-place; but here for std.algorithm.mutation.remove, one need to do ``` array = array.remove(index); // return a new container // v.s. aa.remove(key); // return bool (if it's removed) ``` This in-consistence is really bad.
Comment #16 by ajv-271-109-6446 — 2024-08-21T13:33:13Z
(In reply to Seb from comment #14) > A PR to fix the landmines - https://github.com/dlang/phobos/pull/6154 > > It's probably too late to rename `remove` :/ I'd suggest deprecating it, and adding removeAt and removeValue It is certainly, as several people have commented, a landmine.
Comment #17 by robert.schadek — 2024-12-01T16:18:44Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/phobos/issues/10001 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB