www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - join

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
I implemented a simple separatorless joiner as follows:

auto joiner(RoR)(RoR r)
if (isInputRange!RoR && isInputRange!(ElementType!RoR))
{
     static struct Result
     {
     private:
         RoR _items;
         ElementType!RoR _current;
         void prime()
         {
             for (;; _items.popFront())
             {
                 if (_items.empty) return;
                 if (!_items.front.empty) break;
             }
             _current = _items.front;
             _items.popFront();
         }
     public:
         this(RoR r)
         {
             _items = r;
             prime();
         }
          property auto empty()
         {
             return _current.empty;
         }
          property auto ref front()
         {
             assert(!empty);
             return _current.front;
         }
         void popFront()
         {
             assert(!_current.empty);
             _current.popFront();
             if (_current.empty) prime();
         }
         static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
         {
              property auto save()
             {
                 Result copy;
                 copy._items = _items.save;
                 copy._current = _current.save;
                 return copy;
             }
         }
     }
     return Result(r);
}

The code has a few properties that I'd like to discuss a bit:

1. It doesn't provide bidirectional primitives, although it often could. 
The rationale is that implementing back and popBack incurs size and time 
overheads that are difficult to justify. Most of the time people just 
want to join stuff and go through it forward. The counterargument is 
that providing those primitives would make join more interesting and 
opens the door to other idioms. What say you?

2. joiner uses an idiom that I've experimented with in the past: it 
defines a local struct and returns it. As such, joiner's type is 
impossible to express without auto. I find that idiom interesting for 
many reasons, among which the simplest is that the code is terse, 
compact, and doesn't pollute the namespace. I'm thinking we should do 
the same for Appender - it doesn't make much sense to create an Appender 
except by calling the appender() function.

3. Walter, Don, kindly please fix ddoc so it works with auto. What used 
to be an annoyance becomes a disabler for the idiom above. Currently it 
is impossible to document joiner (aside from unsavory tricks).

4. I found the prime() idiom quite frequent when defining ranges. 
Essentially prime() positions the troops by the border. Both the 
constructor and popFront() call prime().

5. auto and auto ref rock - they allow simple, correct definitions.

6. Currently joiner() has a bug: it will not work as expected for 
certain ranges. Which ranges are those, and how can the bug be fixed?


Andrei
Jan 18 2011
next sibling parent reply so <so so.do> writes:
 2. joiner uses an idiom that I've experimented with in the past: it  
 defines a local struct and returns it. As such, joiner's type is  
 impossible to express without auto. I find that idiom interesting for  
 many reasons, among which the simplest is that the code is terse,  
 compact, and doesn't pollute the namespace. I'm thinking we should do  
 the same for Appender - it doesn't make much sense to create an Appender  
 except by calling the appender() function.

Didn't know there was a solution to namespace pollution. This one is a very nice idea, are you planning to use it in phobos in general? Retro, Stride... there should be many.
Jan 18 2011
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/18/11 2:55 PM, so wrote:
 2. joiner uses an idiom that I've experimented with in the past: it
 defines a local struct and returns it. As such, joiner's type is
 impossible to express without auto. I find that idiom interesting for
 many reasons, among which the simplest is that the code is terse,
 compact, and doesn't pollute the namespace. I'm thinking we should do
 the same for Appender - it doesn't make much sense to create an
 Appender except by calling the appender() function.

Didn't know there was a solution to namespace pollution. This one is a very nice idea, are you planning to use it in phobos in general? Retro, Stride... there should be many.

I plan to, albeit cautiously. Sometimes people would want e.g. to store a member of that type in a class. They still can by saying typeof(joiner(...)) but we don't want to make it awkward for them. Andrei
Jan 18 2011
parent Lutger Blijdestijn <lutger.blijdestijn gmail.com> writes:
Andrei Alexandrescu wrote:

 On 1/18/11 2:55 PM, so wrote:
 2. joiner uses an idiom that I've experimented with in the past: it
 defines a local struct and returns it. As such, joiner's type is
 impossible to express without auto. I find that idiom interesting for
 many reasons, among which the simplest is that the code is terse,
 compact, and doesn't pollute the namespace. I'm thinking we should do
 the same for Appender - it doesn't make much sense to create an
 Appender except by calling the appender() function.

Didn't know there was a solution to namespace pollution. This one is a very nice idea, are you planning to use it in phobos in general? Retro, Stride... there should be many.

I plan to, albeit cautiously. Sometimes people would want e.g. to store a member of that type in a class. They still can by saying typeof(joiner(...)) but we don't want to make it awkward for them. Andrei

I do this sometimes with Appender for splitting complex construction of a string between functions. Is that bad practice? What is the alternative idiom? If possible, please reconsider making Appender an existential type.
Jan 24 2011
prev sibling next sibling parent reply Ary Manzana <ary esperanto.org.ar> writes:
On 1/18/11 4:25 PM, Andrei Alexandrescu wrote:
 I implemented a simple separatorless joiner as follows:

 auto joiner(RoR)(RoR r)

Hi Andrei, What does it do? How do you use it? And why "prime"? When I read it I remember the meaning of "prime number". I just looked up in the dictionary, it seems it also means "prepare". Why not use a simpler language and call it "prepare"?
Jan 19 2011
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/19/11 11:39 AM, Ary Manzana wrote:
 On 1/18/11 4:25 PM, Andrei Alexandrescu wrote:
 I implemented a simple separatorless joiner as follows:

 auto joiner(RoR)(RoR r)

Hi Andrei, What does it do? How do you use it?

Given a range of ranges, joiner concatenates them all with an optional separator. It's just like today's join(). The interesting part is that joiner never allocates memory or copy data - it operates directly on the original ranges. All it needs to do is keep track where it is. Moreover, if you decide to stop the iteration early, some work is saved. import std.algorithm, std.stdio; void main() { auto stuff = [ "coat", "husky", "sled", ]; writeln(joiner(stuff)); writeln(joiner(stuff, "; ")); } writes: coathuskysled coat; husky; sled In brief you can in many places replace join() with joiner() and enjoy fewer memory allocations and less copies.
 And why "prime"? When I read it I remember the meaning of "prime
 number". I just looked up in the dictionary, it seems it also means
 "prepare". Why not use a simpler language and call it "prepare"?

Good idea, done. Will be part of the next commit. I plan to make one more pass through std.algorithm anyway. If there's stuff you wish were there (including stuff generalized from other modules), please let me know. Andrei
Jan 19 2011
parent Lutger Blijdestijn <lutger.blijdestijn gmail.com> writes:
Andrei Alexandrescu wrote:

...
 Good idea, done. Will be part of the next commit. I plan to make one
 more pass through std.algorithm anyway. If there's stuff you wish were
 there (including stuff generalized from other modules), please let me
 know.
 
 
 Andrei

I had need for a group() that constructs a range of ranges instead of tuple with count.
Jan 24 2011
prev sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 1/19/11, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:
 import std.algorithm, std.stdio;
 void main()
 {
      auto stuff = [ "coat", "husky", "sled", ];
      writeln(joiner(stuff));
      writeln(joiner(stuff, "; "));
 }
 coat; husky; sled

This will be great for string mixins. "int first, int last"
Jan 19 2011
prev sibling parent Stanislav Blinov <blinov loniir.ru> writes:
18.01.2011 22:25, Andrei Alexandrescu пишет:
 I implemented a simple separatorless joiner as follows:
 ...

 The code has a few properties that I'd like to discuss a bit:

 2. joiner uses an idiom that I've experimented with in the past: it 
 defines a local struct and returns it. As such, joiner's type is 
 impossible to express without auto. I find that idiom interesting for 
 many reasons, among which the simplest is that the code is terse, 
 compact, and doesn't pollute the namespace. I'm thinking we should do 
 the same for Appender - it doesn't make much sense to create an 
 Appender except by calling the appender() function.

I somewhat disagree about Appender. I had situations when I needed to store an Appender as a class/struct member (i.e. one may build an output range on top of it). Appender!T looks better than ReturnType!(appender!T()), IMHO. Of course, one could always alias that ReturnType, so it's not *that* much of a problem.
Jan 24 2011