July 07, 2011
On 2011-07-07 07:00, James Fisher wrote:
> To date, I've been using D in much the same way I used C++, without heavy use of templates. Now I'm trying out a more functional style using std.algorithm. However I'm pretty much stuck at the first hurdle: map. With type inference, this works:
> 
> import std.algorithm;
> import std.stdio;
> 
> void main() {
> auto start = [1,2,3,4,5];
> auto squares = map!((a) { return a * a; })(start);
> writeln(squares);
> }
> 
> 
> Without type inference (obviously necessary beyond trivial examples), it'd
> be nice to do:
> 
> import std.algorithm;
> import std.stdio;
> 
> void main() {
> int[] start = [1,2,3,4,5];
> int[] squares = map!((a) { return a * a; })(start);
> writeln(squares);
> }
> 
> 
> but this gives "Error: cannot implicitly convert expression (map(start)) of
> type Result to int[]". That opaque type "Result" is weird (not
> parameterized on "int"?), but OK, let's try that:
> 
> import std.algorithm;
> import std.stdio;
> 
> void main() {
> int[] start = [1,2,3,4,5];
> Result squares = map!((a) { return a * a; })(start);
> writeln(squares);
> }
> 
> 
> gives "undefined identifier Result". Not sure why, but OK. I can't see examples in the docs of explicit type declarations -- annoyingly they all use "auto".
> 
> However, they do tell me that map "returns a range". Assuming all definitions of ranges are in std.range, there's no such thing as a "Range" interface, so it's not that. The most general interfaces I can see are InputRange(E) and OutputRange(E). It certainly can't be an OutputRange, so I guess it must satisfy InputRange, presumably type-parameterized with "int". So this should work:
> 
> import std.algorithm;
> import std.stdio;
> import std.range;
> 
> void main() {
> int[] start = [1,2,3,4,5];
> InputRange!int squares = map!((a) { return a * a; })(start);
> writeln(squares);
> }
> 
> 
> But the compiler complains "cannot implicitly convert expression
> (map(start)) of type Result to std.range.InputRange!(int).InputRange".
> That's weird, because "std.range.InputRange!(int).InputRange" doesn't even
> look like a type.
> 
> I've tried all manner of combinations here, but nothing works. Seems like there's something fundamental I'm not understanding.

Really, the idea is that you never care about the return type for most of the functions in std.algorithm and std.range. It's a range, and you use it as a range. That means that it's guaranteed to have certain functions (empty, popFront, front, etc.), with the exact set of operations depending on the type of range that it is. If you need a particular type, then you convert it to what you want (e.g. you use something like std.array.array which converts a range to an array). You really shouldn't be caring about the exact return type of most of the functions in std.algorithm and std.range. And they're pretty much designed with the idea that you _won't_.

Now, if you really need the return type for whatever reason, then use typeof to get the type (be it typeof on the function call - e.g. typeof(map!((a) {return a * a;})(start) - or type of the result which you gave the type of auto). But the idea is that the results of range functions are typically generic ranges where you don't know or care what their exact type is. And since they're ranges, you can even pass them to other range-based functions without caring about what their exact types are.

The one exception that I can think of is that when you need to remove elements from a container, you need the type of the range to either be the exact type of range that that container uses or a range that that container supports (which would generally be the result of take or takeExactly on the type of range that that container uses). Some functions will return the appropriate type (e.g. find), and others won't. But it should generally be obvious from what they do whether they will or not (since they need to return a slice of the original range rather than a new range composed of pieces of the original), and trying the result of a function from std.range or std.algorithm with a remove function for a container should tell you fairly quickly if there's any question.

So, really, the idea is that you don't generally know or care what the return type of a range-based function is and that if you do need it to be a particular type, then you convert it to that type.

- Jonathan M Davis