loading...

List Comprehension in D

jessekphillips profile image Jesse Phillips Updated on ・2 min read

This post is a language comparison coming out of this great article on list comprehension. D does not have List comprehension.

Since D can generally operate on ranges rather than allocated arrays, if you need an array just add .array for more on arrays review

My explanation will be limited to D specific differences, but please ask for further details if something is not clear.

List

import std;
void main()
{
    // arr = [i for i in range(10)]
    writeln(iota(10));
    // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
} 

Iota is uncommon, but is the equivalent of range in Python.

Dictionary

// y = {i:v for i,v in enumerate(x)}
auto x = [2,45,21,45];
auto y = enumerate(x).assocArray;
writeln(y);
// [0:2, 3:45, 2:21, 1:45]

As mentioned D prefers unallocated range manipulation, this tends to mean no index, enumerate creates a tuple with a count and value, and assocArray takes a range of tuple to build an associative array (dictionary)

Conditionals

// arr = [i for i in range(10) if i % 2 == 0]
auto arr = iota(10)
    .filter!(i => i % 2 == 0);
writeln(arr);
// [0, 2, 4, 6, 8]
// arr = ["Even" if i % 2 == 0 else "Odd" for i in range(10)]
auto arr2 = iota(10) 
    .map!(x => x % 2 ? "Odd" : "Even");
writeln(arr2);
//["Even", "Odd", "Even", "Odd", "Even", "Odd", "Even", "Odd", "Even", "Odd"]

Nested for loop

// arr = [[i for i in range(5)] for j in range(5)]
auto arr3 = iota(5)
    .map!(j => iota(5));
writeln(arr3);
// [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]
// arr = [(i,j) for j in range(2) for i in range(2)]
auto arr4 = iota(2).cartesianProduct(iota(2));
writeln(arr4);
//[Tuple!(int, int)(0, 0), Tuple!(int, int)(0, 1), Tuple!(int, int)(1, 0), Tuple!(int, int)(1, 1)]

I find that D makes this behavior very clear.

Tuples being a library provided type, their string representation is a little more verbose.

Flatten 2D Array

// arr = [i for j in x for i in j]
auto x2 = [[0, 1, 2, 3, 4],
 [5, 6, 7, 8, 9]];

auto arr5 = x2.joiner;
writeln(arr5);
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Conclusion

D is a typed language, since I did not convert everything back into an array a unique arr variable was needed for each.

Personally I think D represents the behavior more clearly than Python's list comprehension. Even the conditional output selection, which was more consice in D was represented reasonably.

This does not touch on the other algorithms D provides and can be applied to the ranges.

Python is praised on its clear syntax and readability, well I must be spoiled because it makes me cringe on most everything I see.

Discussion

pic
Editor guide
Collapse
jessekphillips profile image
Jesse Phillips Author

@byrro , in response to dev.to/byrro/comment/j0om

Are you seriously going to stop at iota? I rarely utilize this in real world code, it is generally used in examples as a quick way to get a range.

Note that terminology here is different in D. Python uses 'range' as a sequence of numbers, D uses "a set of different things of the same general type."

Is 'Cartesian product' easy to read for someone who has never crossed path with this operation? No, but it does give terminology to read about what it does. Whereas

[(i,j) for j in range(2) for i in range(2)]

Don't get me wrong, you could easily provide a library function in Python, name it and do this under the hood, it is not like the library implementation is some kind of English statement about the desired outcome.

I'm not saying these are the things which make or break Python's readability, but it does contribute to it not being able to have the throne of readability.

Collapse
byrro profile image
Renato Byrro

You seem to love Dlang and that's totally fine.

But the way you're expressing yourself is not helping to convince others about Dlang, if that's your goal. It's not just me.

I'm a fierce defender of freedom of private initiative and expression. But we've got to think about what is our goal and how to better get there.

If I may suggest an idea, why not approach the issue like: "if you love Python XYZ feature, then you'll love Dlang even more"? That would be more appealing for people to try Dlang.

Collapse
beatbutton profile image
Mitchell Ferree

The built-in module "itertools" has support for Cartesian products, iterator flattening, and more. Comparing one tool in Python, list comprehensions, to arbitrary iterator adaptors in D is disingenuous.

Python has built-in "map" and "filter" functions as well, should you prefer those over comprehensions.

Collapse
jessekphillips profile image
Jesse Phillips Author

Just stumbled acrossed this

medium.com/better-programming/how-...

The issue I see with this is chaining multiple operations. In the example he stores the result before the next operation.

Let's look at an alternative way to make it readable.

// Original breakup, but written in D
auto numbers = [1,2,3,4,5,6];

auto odd_numbers = filter!(n=> n % 2 == 1)(numbers);

auto squared_odd_numbers = map!(n=> n * n)(odd_numbers);

// fold is another name used for reduce 
auto total1 = fold!((acc, n)=> acc + n)(squared_odd_numbers, 0);

// chain operation 
auto total2 = numbers
    .filter!(n=> n % 2 == 1)
    .map!(n=> n * n)
    .fold!((acc, n)=> acc + n)(0);

// Name the lambda operation 
alias odds = x => x % 2 == 1;
alias square = x => x * x;
alias sum = (acc, n)=> acc + n;

auto total3 = numbers
    .filter!odds
    .map!square 
    .fold!sum(0);


assert(total1 == total2);
assert(total3 == total2);

The language isn't the driving force for readability here.

If you look closely D does provide usage challenges since I introduced the use of alias. Explaining its need would be more technical than lambda : is that a bad thing? I don't know.

Collapse
jessekphillips profile image
Jesse Phillips Author

Possibly, do you have examples?

What is the Pythonic way? I showed D idioms.