Previous Entry Share Next Entry
No more loops a D Translation
anime
he_the_great

A post by Dead Code Rising entiled "Java 8: No more loops" covers converting some looping operations and shows how the stream API from Java 8 can make the code more readable. I've implemented the examples using both a class and a struct as the data storage, the revision history shows the changes. Since structs tend to be more common in D I will us it as the dominating example. Be sure to build using -main -unittest since all executing code is within unittest blocks and no main is provided.

dlang
import std.algorithm;
import std.range;
import std.typecons;

private struct Article {

    private immutable string title;
    private immutable string author;
    private immutable string[] _tags;

    private this(string title, string author,
                 immutable(string)[] tags) pure {
        this.title = title;
        this.author = author;
        this._tags = tags;
    }

    @property const(string)[] tags() const {
        return _tags;
    }
}

Here the main difference is that D does not require getters and setters for everything, though if you really are concerned that you'll need to use a function later it is best to do so now.

The only data which utilizes a getter is the tags. This is because we want to return a mutable array of the immutable strings, since the returned array is actually a new stack structure the conversion is safely implicit.

dlang
auto makeArticles() pure {
    return [Article("title""jacob", ["tags"]),
         Article("title""author", ["tags""Java"]),
         Article("title""jacob", ["Java""tags"]),
         Article("title""author", ["tags"]),
    ];
}

This will be a helper function to create our data to be used by our unittests, I could have added a version(unittest) to prevent it being built outside of testing

dlang
bool contains(R, T)(R range, T b) {
    return !range.find(b).empty;
}

public Nullable!(const(Article)) getFirstJavaArticle
                        (const(Article)[] articles) {

    foreach (article; articles) {
        if (article.tags.contains("Java")) {
            return typeof(return)(article);
        }
    }

    return typeof(return)();
}

A small helper template was created that would tell of is the element was contained in the range. This likely could be implemented in Phobos as it can be a little more descriptive than the find equivalent.

This is the only time I'm showing the loop version, since these are structures the return time needs a way to indicate that no Article was found, this has been done by using Nullable which is kind of like the Optional used by Java. Writing out the Nullable type was too annoying so I utilized D's typeof(return) which I could use to encapsulate the item of interest.

dlang
public auto getFirstJavaArticle2
            (const(Article)[] articles) {
    return articles.find!(x => x.tags
                          .contains("Java"));
}

unittest {
    immutable arr = makeArticles();
    assert(arr.getFirstJavaArticle() is arr[1]);

    assert(arr.getFirstJavaArticle2().front is arr[1]);
}

Using std.algorithm.find it is a simple task to locate the first instance based on a predicate. Note however, unlike the looped version in this case the array is being returned and positioned at the first occurrence. This does several things for us, the rest of the data could continue to be processed and we've done away with needing the Nullable. However calling front on an empty range is not defined and I did not verify the range was not empty before doing so.

dlang
public auto getAllJavaArticle(const(Article)[] articles) {
    return articles.filter!(x => x.tags.contains("Java"));
}

unittest {
    immutable arr = makeArticles();

    assert(arr.getAllJavaArticle().equal(arr[1..3]));
}

Attempting to get all the Java Articles is very similar to finding the first occurrence. What I want to note about this is we now have two ways at getting the first occurrence. The first provided a range with the tail of the Articles, this one provides a new range that only includes those Java articles and the front will also be the first occurrence and will have the same seek time as the original use of find.

I will be skipping 'groupByAuthor' the new groupBy function has not yet been released and I have not tested my implementation of groupByAuthor.

dlang
public auto distinctTags(const(Article)[] articles) {
    return articles.map!(x=>x.tags)
        .joiner
        .unqualArray
        .sort
        .uniq;
}

import std.traits;
@trusted Unqual!(ForeachType!Range)[]
                 unqualArray(Range)(Range r)
              if (isIterable!Range
                  && !isNarrowString!Range
                  && !isInfinite!Range)
{
    return cast(typeof(return)) r.array;
}


unittest {
    auto arr = makeArticles();

    assert(arr.distinctTags.equal(["Java""tags"]));
}

Grabbing the distinct tags from all Articles is an interesting task because unlike the previous examples this actually requires a heap allocation to accomplish. We start by building a range for all the tags using the map.joiner.

The main goal here is to apply the call of 'uniq' to this new range of all tags (i.e. make a list of uniq tags). However to keep uniq operating in efficient time and space it relies on having all the data sorted, a sorted range can be traversed lazily and does not need a collection of previously found items. It is unfortunate that std.algorithm.uniq will still operate on something other than a std.range.SortedRange without a compile error.

Well sorting requires the ability to swap data, and we are working with immutable strings, this prevents making modifications as that would distort the order of the original immutable array we had defined. In other words, "yay const is keeping us from modifying an ordering of data which may actually be important in its original form!"

Well Phobos doesn't provide a way create a new mutable array from a range of immutable elements. std.array.array will allocate new memory and copy over the data which means even if the original was immutable it no longer has to be. I haven't thought through and tested all the edge cases, but I created a little helper function unqualArray that just asserts that new data is returned and can be mutated. This allows me to create an unqualified array which can then be sorted making it a perfect candidate to mask as a range of unique elements.

Conclusion

D does provide the principles that are found in the functional approach. It is missing some of the functions others may be familiar with by name and some more work is needed to bring const into the flow.

What we can see is that even starting with immutable data objects we can, for the most part, search the data to locate what is of interest. And by designating the mutability of our data the language prevent what could have been harmful mutation utilized in our search for uniqueness. D has come a long way in this regard!

Also unlike the examples provided for Java, D does not collect the elements into a new collection for each operation of the range. Instead return a range that maps itself as a view of the originals, except in the explicit case that a new collection (unqualArray) had to be created.


?

Log in