Hacker News new | ask | show | jobs
by EvenThisAcronym 2438 days ago
Let me give you a little guidance on this (if you happen to still be using D). Most functions in std.algorithm (and many more throughout the rest of the standard library) return "ranges", which is like a begin iterator and an end iterator zipped up into one data structure. There is a "range hierarchy" that mirrors the C++ iterator hierarchy:

Input/Output Range > Forward Range > Bidirectional Range > Random Access Range

Input ranges provide the following API:

    bool empty()
    T front()
    void popFront()
Forward ranges provide the same API, and also add a "save" function that creates a copy of the range's current state.

Bidirectional ranges add "back" and "popBack" functions, which don't really need any explanation.

Random access ranges additionally add a "length" function and constant-time array-like indexing.

In addition to most functions in std.algorithm _returning_ ranges, most of them also _accept_ ranges. Therefore, you can freely pass the result of any function in std.algorithm into another function that takes a range, and it should all just work. It's pretty much the same deal as in Rust/Java/Swift/etc. with their various iterator protocols, although the API for ranges in D is more influenced by C++.

For example, the first Project Euler problem, using a range-based approach:

    import std.algorithm;
    import std.range;
    import std.stdio;

    void main()
    {
        //List all the natural numbers below 1000 that are multiples of 3 or 5
        auto natsBelow1000 = iota(1000); //iota returns a range that lazily computes the numbers from 0-999
        auto multiplesOf3Or5 = natsBelow1000.filter!(n => n % 3 == 0 || n % 5 == 0); //filter accepts a range as an argument, which it lazily filters according to the given predicate
        auto sum = multiplesOf3Or5.sum(); //Sum accepts a range of "summable" items and computes the... sum
        writeln(sum); //Prints 233168
    }
A D programmer wouldn't generally write things this way, preferring to make a more idiomatic "range chain" like you'd see with Java's streams or Rust's iterators, but you get the idea.

As you might have guessed, arrays are also ranges - random access ranges - which is why they're so easy to use.

I would highly recommend against putting `.array` everywhere in your code, as it will force the range you pass to it to be eagerly evaluated and allocate more memory from the GC. It's much better to take some initial data store, like an array, do all your filtering and mapping, etc. on it, and when you're done, _then_ turn it back into an array with a call to `.array`. That way you'll get all the lazy iteration and low memory footprint of ranges, and then at the end you have a nicely allocated array of data with the result that is easy to work with.

Honestly, though, in most of my code I usually just pass around ranges as they're very good for creating interlocking adapters over different backing stores. One really great article on advanced usage of ranges is H. S. Teoh's article on Component Programming with Ranges: https://wiki.dlang.org/Component_programming_with_ranges

A couple great resources on ranges in D:

http://www.informit.com/articles/printerfriendly/1407357

https://tour.dlang.org/tour/en/basics/ranges

http://ddili.org/ders/d.en/ranges.html

http://dconf.org/2015/talks/davis.html

2 comments

auto natsBelow1000 = iota(1000);

How do I pass this output to some other function without doing .array()? It's rare that you do all your processing in one method. Should I do this:

void processFurther(T)(T t) if isInputRange!T { } ?

I mean, it would probably work, but I don't like how the conditions are detached from type definitions :S

Yes, or if you don't want to use a template function, for whatever reason, you can wrap the result of iota(1000) into an InputRangeObject (see https://dlang.org/phobos/std_range_interfaces.html#InputRang... for an example).

> I don't like how the conditions are detached from type definitions

I can understand that; it's something that takes a bit of getting used to. See https://dlang.org/concepts.html for an explanation of template constraints and why they were designed this way.

Uniform Function Call Syntax is one of my favourite things about D