Halp! type system (__expand_field_0 error), compile time/runtime questions (AoC-2017 puzzle spoilers inside)

Steven Schveighoffer schveiguy at yahoo.com
Fri Dec 15 01:43:04 UTC 2017


On 12/14/17 5:56 PM, aliak wrote:
> On Thursday, 14 December 2017 at 16:38:26 UTC, Steven Schveighoffer wrote:
>> b) if the above actually does compile for you, what is the minimal 
>> code that does not?
> 
> Yes of course, here's a minimal program that does not compile for me:
> 
>    import std.stdio, std.algorithm, std.range, std.array;
> 
>    int rotate(int[] lengths) {
>      foreach(skip, length; lengths.enumerate) {}
>      return 0;
>    }
> 
>    auto knotHash(string input) {
>      return [1].rotate();
>    }
> 
>    auto data = ["string"]
>      .map!knotHash
>      .array;
> 
>    void main() {}
> 

Ahh. So the difference you were missing is that when you assign a static 
global an initializer, that initializer is evaluated at *compile time* 
using CTFE. That is, your entire algorithm is executed at compile-time, 
and at runtime you just see the final results as an array.

If you assign it inside a function, it's done at runtime.

So the CTFE interpreter doesn't like something to do with your chain of 
ranges. This is not totally unexpected, as the CTFE engine has lots of 
quirks that make it sometimes puke on valid CTFE-able code.

To answer your questions below:

> 
> The above code, however, will compile with any of the following changes, 
> and I don't understand why for any of them:
> 1) remove .enumerate

enumerate is probably the culprit. I think possibly CTFE doesn't 
properly handle Tuples correctly.

> 2) move the auto data inside the body of main

This makes it run at runtime, not compile-time.

> 3) remove the call to .array

And this is even more interesting :) Map is lazy, so what you have done 
when you remove the array call is generate a map struct, and assign it 
to data. This means that it didn't actually run enumerate or knotHash at 
all! It just has a struct with a string "string" in it, and is ready to 
run knotHash as soon as you want to iterate it. This is why it doesn't 
fail in this case.

When .array is present, it evaluates all the elements in the range 
eagerly, therefore running everything at compile time, and reaching the 
error with enumerate that CTFE cannot handle something.

> 
>> All that aside, you may not realize, this works as well:
>>
>> foreach(skip, length; lengths)
> 
> Sweet, thanks! Yeah that works too.
> 
> Another thing I realized is that if I switch from .enumerate to the 
> foreach you suggest (in my non minimized example) the compile time 
> increases by A LOT. From about 1 second to 70 seconds.

CTFE has a really ...interesting property that it never mutates data. So 
every loop, it allocates another integer, for instance. Most of the 
time, if a D program is taking a long time compile, it's using a lot of 
CTFE (see just about every dconf talk about how CTFE being slow sucks :)

This makes it really really bad at looping over large sets of data. 
Stefan Koch is working on fixing a lot of the problems with CTFE.

Note that I don't think enumerate is any faster in CTFE, either that 1 
second means that it is the lazy version (which doesn't really do much 
at compile-time), or it is the version that doesn't compile.

At this time, I'd recommend doing your task at runtime anyway. Hopefully 
I have shed some more light on how things are working here.

-Steve


More information about the Digitalmars-d-learn mailing list