DEV Community

Jesse Phillips
Jesse Phillips

Posted on • Edited on

List Comprehension in D

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]
} 
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode
// 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"]
Enter fullscreen mode Exit fullscreen mode

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]]
Enter fullscreen mode Exit fullscreen mode
// 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)]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (5)

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
jessekphillips profile image
Jesse Phillips

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 • Edited

Possibly, do you have examples?

What is the Pythonic way? I showed D idioms.

Collapse
 
jessekphillips profile image
Jesse Phillips

@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.