www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.ideas - takeBack() implementation in std.range for OutputRanges

reply Salih Dincer <salihdb hotmail.com> writes:
Why have a function called dropBack() but not takeBack()?
---

**Rationale:** It uses the drop() function in the module and is 
compatible with the name dropBack().

Let's say we have a range (called STS for short) that returns odd 
numbers:

```d
alias STS = SonsuzTekSayı;
struct SonsuzTekSayı { // InfinityNumbers
   import std.traits;
   import std.bigint;

   BigInt numara;
   size_t length;

   this(T)(T ilk) { // Initialize
     static if (isIntegral!T)
       numara = BigInt(ilk % 2 ? ilk : ++ilk);
     else {
       import std.string : isNumeric;
       if (ilk.isNumeric) {
         auto birler = cast(char)ilk.back;
         if (birler % 2 == 0) ++birler;
         ilk.length--;
         numara = BigInt(ilk ~ birler);
       }
     }
   }
   bool empty;
   auto front() => numara;
   auto popFront() => numara += 2;
}
```

Let's add a convenience function to increase efficiency:

```d
alias adetSayıVer = startingFrom;
auto startingFrom(T)(size_t Length, T First)
{
   import std.range : take;

   auto range = STS(First);
   range.length = Length;

   return range.take(Length);
}
```

Since our range is not Bidirectional, we get an error with 
```dropBack()```. Also, we cannot pop 4 elements from the end 
like we do in an array (```arr[$ - 4..$]```), my solution is (if 
it gives the range length) the following:

```d
auto takeBack(R)(R range, size_t n)
{
   import std.range : drop;

   auto diff = range.length - n;

   return range.drop(diff);
}

void main()
{
   import std.stdio : writeln;
   auto r = 1000.startingFrom("9000");

   r.takeBack(4).writeln; /* PRINTS:

     [10993, 10995, 10997, 10999]

   */
}
```

**FN:** Although it is not needed at all, takeBack() already 
works with an array. Perhaps the slicing method for arrays could 
be adapted with or without a convenience function.

SDB 79
Aug 31 2024
parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Saturday, 31 August 2024 at 17:55:17 UTC, Salih Dincer wrote:
 Why have a function called dropBack() but not takeBack()?
probably retro.take is considered good enough? When retro.drop.retro isnt
Aug 31 2024
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Saturday, 31 August 2024 at 22:29:57 UTC, monkyyy wrote:
 On Saturday, 31 August 2024 at 17:55:17 UTC, Salih Dincer wrote:
 Why have a function called dropBack() but not takeBack()?
probably retro.take is considered good enough? When retro.drop.retro isnt
The problem is that, whether retro() or dropBack() works with BidirectionalRange. So we need to find different solutions. If everything was an array, things would be easier, but I don't know how to get the last 4 elements of a range of known length, other than filtering! Does anyone have another practical solution? SDB 79
Sep 01 2024
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, September 1, 2024 3:16:31 PM MDT Salih Dincer via dip.ideas wrote:
 On Saturday, 31 August 2024 at 22:29:57 UTC, monkyyy wrote:
 On Saturday, 31 August 2024 at 17:55:17 UTC, Salih Dincer wrote:
 Why have a function called dropBack() but not takeBack()?
probably retro.take is considered good enough? When retro.drop.retro isnt
The problem is that, whether retro() or dropBack() works with BidirectionalRange. So we need to find different solutions. If everything was an array, things would be easier, but I don't know how to get the last 4 elements of a range of known length, other than filtering! Does anyone have another practical solution?
The fact that a range's length is known is not sufficient to be able to get at the elements at its end. In most cases, a range with length is a bidirectional range or random-access range, but it is possible to have a forward range with length (e.g. because takeExactly was used to create it), and the forward range API provides no direct way to get at the elements at its end. Fundamentally, you cannot get at the end of a range that isn't a bidirectional or random access range without popping off all of the preceding elements. The API gives us no way to do it, and plenty of ranges cannot implement that kind of functionality (otherwise, they'd be bidirectional or random-access ranges). If you have a random-access range, they're generally sliceable (and probably should require that they be sliceable, but IIRC, they currently don't for some reason), so you can just slice the end (and if for some weird reason a random-access range doesn't have slicing, it's trivial to create a wrapper that does the same thing by using the indices). And if you have a bidirectional range, then you can use retro and take (though you then have a range in the reverse order and can't use retro on the result, since bidirectional ranges provide no way to access the middle of a range). If you want to be able to just take the last n elements of a range and have them be in the same order, you need a random-access range. The range API provides no other way to do it, and if a range is not a random-access range, then it's usually because it can't efficiently be one for one reason or another (though in some cases, it's because the programmer didn't bother to add the full range capabilities that they could, because they didn't need them for what they were doing). If you want to do something like fwdRange.takeBack(5), then that's simply never going to work unless the original range is a random-access range, because that range type doesn't provide a way that that could be implemented efficently. - Jonathan M Davis
Sep 01 2024
next sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
Last year, we have been examining a significant design question 
within the context of range functionality:

This critical analysis aims to provide a deeper look into the D 
language's range philosophy, clarifying the issue with the 
following evidence and rationale:

**Symmetry and Interface Integrity**

**Rationale:** Functions like ```take()``` and ```drop()``` in 
our standard library module ```std.range``` handle operations 
from the front, while ```dropBack()``` allows for removing 
elements from the end. Logically, having a ```takeBack()``` 
function to take elements from the end would complete the 
interface symmetrically, improving code readability and offering 
users complete uniformity.

**D Language Range Philosophy and Performance Concerns**

The core design of D language ranges (being lazy and potentially 
infinite) is the fundamental obstacle preventing the inclusion of 
```takeBack()``` as a standard function:

**Evidence 1:** ```SizedRange``` Requirement:
The proposed ```takeBack(R range, size_t n) => 
range.drop(range.length - n)``` implementation inherently 
requires the range's length (```.length```) to be known. This 
mandates that the range conforms to the SizedRange concept. 
Infinite or length-unknown ranges cannot use this function.

**Evidence 2:** ```BidirectionalRange``` Preference:
The D library typically prefers the BidirectionalRange 
requirement for end-of-range operations (such as ```popBack``` or 
```dropBack```). This guarantees that access to the end is both 
reliable and efficient.

For Forward Ranges, calculating ```.length``` may necessitate 
consuming (iterating over) the entire range. This contradicts the 
philosophy of lazy evaluation and introduces a potentially 
detrimental performance cost. To avoid the risk of such cost 
without knowing the range's type, the library restricts functions 
focused on the back end to stricter range types (usually 
BidirectionalRange or higher).

The absence of a ```takeBack``` function in the D standard 
library is largely due to the emphasis on performance and 
laziness in D's range design. The library prioritises 
BidirectionalRange for end-of-range operations to ensure 
efficiency.

However, for ranges that accept the ```SizedRange``` constraint, 
the proposed single-line implementation 
(```range.drop(range.length - n)```) is extremely clean, useful, 
and practical. This makes it a valid proposal for consideration 
as a utility function to be added to the standard library, 
specifically for Sized Forward Ranges.

This discussion once again highlights how philosophical decisions 
and practical needs intersect in our library development process. 
We welcome your input and potential Pull Requests on this matter.

Yours faithfully,
Nov 22
parent Paul Backus <snarwin gmail.com> writes:
On Sunday, 23 November 2025 at 00:07:53 UTC, Salih Dincer wrote:
 Last year, we have been examining a significant design question 
 within the context of range functionality:

 This critical analysis aims to provide a deeper look into the D 
 language's range philosophy, clarifying the issue with the 
 following evidence and rationale:
Please don't post LLM-generated text on the forums. It's disrespectful to all of the people who take the time to read and respond to posts here in good faith.
Nov 27
prev sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Monday, 2 September 2024 at 02:25:24 UTC, Jonathan M Davis 
wrote:
 If you want to do something like fwdRange.takeBack(5), then 
 that's simply never going to work unless the original range is 
 a random-access range, because that range type doesn't provide 
 a way that that could be implemented efficently.

 - Jonathan M Davis
You are right, it is impossible to implement ```takeBack``` efficiently on a generic ```ForwardRange```. However, my proposal explicitly imposes the requirement of a ```SizedRange```, where the length is known beforehand. Within this constrained scope, the function is essentially a call to ```drop()``` ```(range.drop(range.length - n)) ```. Our goal is to add a convenience function that facilitates this manual calculation, which is already valid, by providing a naming symmetry with ```dropBack```. In the current library, there is a lack of an intuitive way to cleanly take the end of ranges that are not ```BidirectionalRange``` but whose length is known. SDB 79
Nov 22