Thread overview
Coercing ranges to the same type
Jul 06, 2015
Matt Kline
Jul 06, 2015
Alex Parrill
Jul 06, 2015
Matt Kline
Jul 06, 2015
Matt Kline
Jul 08, 2015
Jesse Phillips
July 06, 2015
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.
July 06, 2015
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
July 06, 2015
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.
July 06, 2015
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);
    }
}

July 08, 2015
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
}