www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Coercing ranges to the same type

reply "Matt Kline" <matt bitbashing.io> writes:
Say I'm trying to expand an array of file and directory paths 
(such as ones given as command line args) into a range of file 
paths I can iterate over. A simplified example might be:

auto getEntries(string[] paths, bool recursive)
{
     auto files = paths.filter!(p => p.isFile);

     if (recursive) {
         auto expandedDirs = paths
             .filter!(p => p.isDir)
             .map!(p => dirEntries(p, SpanMode.depth, false))
             .joiner
             .map!(de => de.name); // back to strings

         return chain(files, expandedDirs);
     }
     else {
         return files;
     }
}

Even though both return statements return a range of strings, 
this doesn't compile because the result of `chain` is a different 
type than the result of `filter`. Is there some generic range I 
could coerce both ranges to in order to have the same return type 
and make this work? .array is a non-starter since it throws out 
the ranges' laziness.
Jul 06 2015
next sibling parent reply "Alex Parrill" <initrd.gz gmail.com> writes:
On Monday, 6 July 2015 at 19:46:51 UTC, Matt Kline wrote:
 Say I'm trying to expand an array of file and directory paths 
 (such as ones given as command line args) into a range of file 
 paths I can iterate over. A simplified example might be:

 auto getEntries(string[] paths, bool recursive)
 {
     auto files = paths.filter!(p => p.isFile);

     if (recursive) {
         auto expandedDirs = paths
             .filter!(p => p.isDir)
             .map!(p => dirEntries(p, SpanMode.depth, false))
             .joiner
             .map!(de => de.name); // back to strings

         return chain(files, expandedDirs);
     }
     else {
         return files;
     }
 }

 Even though both return statements return a range of strings, 
 this doesn't compile because the result of `chain` is a 
 different type than the result of `filter`. Is there some 
 generic range I could coerce both ranges to in order to have 
 the same return type and make this work? .array is a 
 non-starter since it throws out the ranges' laziness.
They aren't actually the same types; one is a `FilterRange!(string[])`; the other a `ChainRange!(string[], MapRange!(...))`. Since they're structs, there's no runtime polymorphism. You can either make `recursive` a template argument (`auto getEntries(bool recursive)(string[] paths)`) with `static if` if you know at compile time when to recurse or not, or use a class wrapper in std.range.interface [1]. [1]: http://dlang.org/phobos/std_range_interfaces.html
Jul 06 2015
parent reply "Matt Kline" <matt bitbashing.io> writes:
On Monday, 6 July 2015 at 21:35:53 UTC, Alex Parrill wrote:

 They aren't actually the same types
I understand the problem - I was just wondering if there was a standard library solution to this or if I would have to roll my own.
 use a class wrapper in std.range.interface [1].

 [1]: http://dlang.org/phobos/std_range_interfaces.html
I think I'll go with this one, since the use case would be similar to the '-r' flag in standard Unix utils (copy, mv, etc.) where the user specifies if they want to recurse through provided directories.
Jul 06 2015
parent "Matt Kline" <matt bitbashing.io> writes:
As it turns out, inputRangeObject does an excellent job at this 
task. The solution then becomes something like:

InputRange!string getEntries(string[] paths, bool recursive)
{
     auto files = paths.filter!(p => p.isFile);

     if (recursive) {
         auto expandedDirs = paths
             .filter!(p => p.isDir)
             .map!(p => dirEntries(p, SpanMode.depth, false))
             .joiner
             .map!(de => de.name);

         return inputRangeObject(chain(files, expandedDirs));
     }
     else {
         foreach (dir; paths.filter!(p => p.isDir))
             stderr.writeln("omitting directory " , dir);

         return inputRangeObject(files);
     }
}
Jul 06 2015
prev sibling parent "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Monday, 6 July 2015 at 19:46:51 UTC, Matt Kline wrote:
 Say I'm trying to expand an array of file and directory paths 
 (such as ones given as command line args) into a range of file 
 paths I can iterate over. A simplified example might be:

 auto getEntries(string[] paths, bool recursive)
 {
     auto files = paths.filter!(p => p.isFile);

     if (recursive) {
         auto expandedDirs = paths
             .filter!(p => p.isDir)
             .map!(p => dirEntries(p, SpanMode.depth, false))
             .joiner
             .map!(de => de.name); // back to strings

         return chain(files, expandedDirs);
     }
     else {
         return files;
     }
 }

 Even though both return statements return a range of strings, 
 this doesn't compile because the result of `chain` is a 
 different type than the result of `filter`. Is there some 
 generic range I could coerce both ranges to in order to have 
 the same return type and make this work? .array is a 
 non-starter since it throws out the ranges' laziness.
I'd say, try to move 'recurse' into a compile time variable, if you need it runtime, move it up a layer: import std.file; import std.range; import std.algorithm; void main(string[] args) { import std.stdio; auto recurse = true; if(recurse) args.getFiles.chain(recurseForFiles(args[])).writeln; else args.getFiles.writeln; } auto getFiles(string[] paths) { return paths.filter!(p => p.isFile); } auto recurseForFiles(string[] paths) { return paths .filter!(p => p.isDir) .map!(p => dirEntries(p, SpanMode.depth, false)) .joiner .filter!(p => p.isFile) .map!(de => de.name); // back to strings }
Jul 07 2015