On Monday, 10 February 2014 at 21:47:35 UTC, Ty Overby wrote:
I have to be missing something really basic. It couldn't
possibly be this hard to remove an element from an array.
I think the correct solution is:
systems.linearRemove(systems[].find(system)[0 .. 1]);
Usually containers have an overload of *[Rr]emove that takes
`Take!Range`, allowing:
systems.linearRemove(systems[].find(system).takeOne());
std.container.Array not having overloads for `Take!Range` looks
like an oversight.
To the uninitiated this can look ridiculous, but hear me out
first.
First of all, the unary slice operation (the empty brackets: [])
is the container primitive to get a range spanning the entire
container. It is arguably the most fundamental container
primitive, and is crucial when using other container primitives,
as they often take range parameters. IMO, it would be a nice
compiler enhancement to raise a tailored error message in cases
where [] is forgotten (probably non-trivial to implement, though).
With that out of the way: in D, both ranges and containers follow
the principle that primitives are subject to restrictions on
algorithmic complexity to avoid the quadratic complexity
minefield that the Java approach heralds through deceptive
abstraction of linear algorithms (of which
ArrayList/List/Collection.remove is a good example). Therefore, a
primitive equivalent of Java's List/Collection.remove is not
welcome in D.
Often when linear search is required for this kind of operation,
the user has chosen the wrong data structure, so the absence of
deceptive abstractions from the core API is *usually* a
non-issue. Of course, sometimes linear search is perfectly
justified, especially when it comes to (smallish) arrays, or code
that is only run very rarely. In these cases, the equivalent D
code using range algorithms (shown above) has advantages; we can
glean two important artefacts: a) linear search is at play
(because of `find`), and b) only the first element found is
removed. The obvious disadvantage is that writing such code in
the first place requires a relatively high degree of familiarity
with Phobos containers and ranges. That said, it's probably
offset by the fact that when you *do* start picking up Phobos'
generic algorithms and primitives, you can apply them everywhere
- learning the API of a new container is just a matter of
glancing over what primitives it supports. Better documentation -
such as adding examples - is definitely in order to ease this
learning, though.