On Wednesday, 10 April 2019 at 10:18:35 UTC, H. S. Teoh wrote:
The functionality rocks, but the syntax is a horrendous hairball of inconsistencies and design-by-chance.

There's a little bit weird about it, but it makes sense.

I used to find it hard to even remember how it works, but then I had to describe it for the book and it all came together. Consider this.

Declaring a normal local variable:

---
int foo = 0;
---

We can generalize that to
---
Type_Name Local_Name Operator Initial_Value
---

Well, now, compare to the is expression:

---
is(typeof(func) Params == __parameters)
---


It is a bit of a stretch, but the general shape is the same. We can define the is expression generally to be:

is(Type_Name Local_Name Deconstruction_Pattern)


And then both Local_Name and Deconstruction_Pattern are optional.

This gives us all the forms from the website https://dlang.org/spec/expression.html#IsExpression (from point #5 there)

1: is(Type). This has the type, but left out the local name and deconstruction pattern. All it cares about is if the type exists.

2: is(Type : something). This deconstruction pattern, using :, means "can implicitly convert to something"

The deconstruction pattern mimics common ways of writing such types; class A : B {} defines a type A that can implicitly cast to B, so is(A : B) would pass and return true.

3: is(Type == something). This deconstruction pattern, using ==, just needs an exact match. It is probably the easiest one for people to remember.

4: is(Type Identifier). This skips the deconstruction pattern, meaning it is the most basic "this type must exist" check, but includes the local name.

Like with `int a;`, the name comes after the type.

5: is(Type Identifier : something). This is just #2 again, but including the optional local name.

6: is(Type Identifier == something). Just #3 including the optional local name.

At this point, the documentation also includes other deconstruction patterns, like the `class` keyword, etc. These are mostly very simple, but it is slightly magical in places because it can initialize Identifier to other thing.. but is that really weird?

int a = 10;

The value of a is dependent on the right side, and so is the Identifier here.

It looks totally wrong because it is using == here rather than =, so we intuitively think

is(A B == C)

is comparing B and C... but just rewrite in your brain that this is actually declaring a variable with a funky deconstruction initializer and it will make sense again.

So the whole `== C` part is the deconstruction pattern, and B is the variable being set to it. And being a deconstruction pattern, it is trying to pull layers off, so stuff like

is(MyClass Parents == super),

the ==super pattern is deconstructing its super keyword; its base classes.

Think of it not as comparison, but as matching a deconstruction pattern and it will make more sense.

I know its weird, but we can work with it. Let's move on:

7: This one has a lot of examples, but it really just expands on the deconstruction pattern. For example:

static if(is(int[10] == T[N], T, size_t N))

What's the pattern here?

== means use exact comparison, not implicitly converting comparison.

T[N] writes out a model of what the declaration is expected to look like.

Then commas separate the definitions of each placeholder variable, just as if they were template argument definitions. Same syntax, different location.

The one tricky thing is the compiler will not tell you when you malformed the pattern (for the most part), just nothing will happen to match it. So match it on some known quantity to test via static assert or something.

To use a complex pattern with the optional name:


static if(is(int[10] OriginalType == T[N], T, size_t N))

that's all the pieces together. Not so bad when you know how it breaks down.

Reply via email to