Ruby-style "each" in D?

monarch_dodra monarchdodra at gmail.com
Fri Mar 21 04:02:58 PDT 2014


On Friday, 21 March 2014 at 00:42:00 UTC, Walter Bright wrote:
> On 3/20/2014 5:33 PM, bearophile wrote:
>> As I explained in a precedent post in this thread it's hard to 
>> design something
>> if you don't try to use it, even something simple as a each().
>
> Right on.

So I toyed around a little. I played with mostly tee/tap and 
each. The first observation is that both can help make a "2D" 
ufcs chain, which is pretty nice. It makes more sense (IMO) to 
code it that way, than to use awkward "join", to concatenate the 
"2D" range into a continuous "1D" range, eg: join(lines, "\n");

First: "tee".

I wanted to try to experience the "multiple tees" approach. I 
made the code purposefully verbose, so as to better test it. 
Because I wanted to process each item once, and only once, I used 
the default policy of `Yes.pipeOnPop`. I did some tests with both 
"tee!pred" and "tee(range, output)", but they were functionally 
equivalent. Anyways, here was my first iteration:

//----
     size_t N = 5;
     chain(iota(0, N), only(N), iota(0, N).retro) //0 .. N .. 0
         .tee!((a)=>' '.repeat(N - a).write())()  //leading spaces
         .tee!((a)=>'*'.repeat(a).write())()      //first half of 
stars
         .tee!((a)=>'*'.write())()                //middle star
         .tee!((a)=>'*'.repeat(a).write())()      //second half
         .tee!((a)=>writeln())()                  //linefeed
         .reduce!"0"(0);                          //or consume/walk
//----

Surprise! It's wrong!

each "tee" triggers on the call to `popFront`. It does its job 
(calls pred(r.front)), and then push the "pop down the chain. 
What this means is that my "tee's" are actually executed right to 
left! Talk about counter intuitive.

So *this* is correct:
//----
     size_t N = 5;
     chain(iota(0, N), only(N), iota(0, N).retro) //0 .. N .. 0
         .tee!((a)=>writeln())()                  //linefeed
         .tee!((a)=>'*'.repeat(a).write())()      //second half
         .tee!((a)=>'*'.write())()                //middle star
         .tee!((a)=>'*'.repeat(a).write())()      //first half of 
stars
         .tee!((a)=>' '.repeat(N - a).write())()  //leading spaces
         .reduce!"0"(0);                          //or 
consume/walk/each()
//----

Odd!

Second:
Each tee pipes a call to front, and *then* calls front again when 
popped. Effectively, if you have *N* tee's in your range, you'll 
call "fun" roughly N*N/2 times. Not great.

This might be an unfair assesment of "tee", because I over-used 
it on purpose, but I think it *does* show that it's not scaling 
well, and that it is triggering in a confusing order.

Also:
//----
     size_t N = 5;
     foreach ( a ; chain(iota(0, N), only(N), iota(0, N).retro))
     {
         ' '.repeat(N - a).write();
         '*'.repeat(a).write();
         '*'.write();
         '*'.repeat(a).write();
         writeln();
     }
//----
Just saying. Why would I use "tee" when I have that?

But I think I'm using "tee" wrong: The idea is to "hook" it into 
a chain that actually does something. It shouldn't be the main 
"doer".

---------------------------------------------

What about each?

I wrote this:
//----
chain(iota(0, N), only(N), iota(0, N).retro)
     .each!(
         (a) => writeln(' '.repeat(N - a), '*'.repeat(a*2+1))
     )();
//----

I think this is a fair assesment of how "each" would be used? It 
looks nice, and is relatively short. But then I wrote this:

//----
foreach ( a ;
     chain(iota(0, N), only(N), iota(0, N).retro) )
{
     writeln(' '.repeat(N - a), '*'.repeat(a*2+1)))
}
//----

Hum... you still get the same functional initial declaration, but 
the "foreach" doesn't get in the way, while still keeping a clear 
functional/imperative distinction.

------------------------------------------

Si that's my initial assessment.

I'm not really sold on "each". I think it'll be abused by those 
that want a "1-liner" at all cost, leading to an image that "D is 
nothing but horrible 1 liners!", all that without providing any 
real functionality.

As for "tee": I'm convinced I didn't use it for its intended 
function, but I think it shows its function can easily be 
hijacked to do things it wasn't meant for.


More information about the Digitalmars-d mailing list