Range of n lines from stdin

Jakob Ovrum jakobovrum at gmail.com
Sat Dec 28 00:56:52 PST 2013


On Friday, 27 December 2013 at 20:30:52 UTC, Ivan Kazmenko wrote:
> Hmm?..  From my experience, attempting to use a range in a 
> wrong way usually results in a compilation error.  For example, 
> I can't do
> n.iota.map!(_ => readln).sort())
> since MapResult isn't a random access range with swappable 
> elements. I can instead do
> n.iota.map!(_ => readln).array().sort())
> and it allocates an array and works as expected.  So, how do I 
> misuse that range?

Yes, the idea is that ranges only present interfaces that make 
sense, so cases of misuse will result in a compilation error. 
However, hacks like using map with functions that ignore their 
argument(s) throws that out of the window: `r` in `auto r = 
n.iota.map!(_ => readln);` claims to support forward, 
bidirectional and random access (all read-only, as the argument 
function returns by value) as well as slicing, but none of these 
make any sense; all access primitives do exactly the same thing, 
with the result being different every time. Even the simplest 
invariants fail, such as `r.front == r.front`, and `popFront`, 
`popBack` and slicing only has a binary effect, whether or not 
the range is empty yet.

> Hmm.  For example, that could be a RNG emitting (a range of) 
> random numbers, then "empty" is always false.  But we still 
> want a new random number each time.  Something like
> n.iota.map!(_ => uniform(0, 10))

That would only provide `n` random numbers, not an infinite 
number.

All the random number generator types in `std.random` are 
infinite forward ranges of random numbers, which is completely 
fine. For any PRNG `r`, `r.front == r.front` is true, and remains 
the same number until `r.popFront()`, it correctly has no length 
and is always non-empty (infinite range), and `r.save` works 
correctly etc.

> So, something like
> n.iota.map !(_ => readln).writeln;
> is bad style but
> writeln (n.iota.map !(_ => readln));
> better shows what's the main action?  Makes sense for me.

No, it has nothing to do with syntax. The two examples are 
completely equivalent, and the only problem is that it breaks the 
invariant that the result of map's transformation function should 
be derived from the arguments it was given. The fact that the 
transformation function is impure is not in itself a problem: 
pure functions can also ignore arguments, and impure functions 
can return consistent results while still being necessarily 
impure.

> Perhaps there's a wholly different way of thinking about this 
> in which the first definition makes much more sense than then 
> second one from the start.  If so, please share it.

All you have to do is look at the signature of the function, 
which is the primary part of its documentation:

Repeat!T repeat(T)(T value);

It takes one value of any type T, not a function pointer or 
delegate that returns T. Even if you give it a function pointer 
or delegate (which your example does not), it will simply repeat 
that function pointer or delegate, never calling it.

As I already explained, `readln.repeat(n)` is just a different 
way of writing `readln().repeat(n)` which in turn is also 
equivalent to `repeat(readln(), n)`. This should make it 
perfectly clear what it does - `readln` is called and its return 
value is passed to `repeat`. Barring one relatively obscure 
exception[1], this is the only way to interpret the expression 
regardless of the signature of the function, as a consequence of 
basic languages rules common to the entire C family of 
programming languages.

[1] ... in D we have something (slightly controversial) called 
the `lazy` parameter storage class, but when used, it is clearly 
visible in the signature of the function. 
http://dlang.org/function.html#parameters


More information about the Digitalmars-d-learn mailing list