Skip to content

[articles/mixin.dd] Add concatenation, statements, types, expressions #3209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 113 additions & 13 deletions articles/mixin.dd
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,130 @@ Ddoc

$(D_S String Mixins,

$(P String Mixins (not to be confused with
$(HEADERNAV_TOC)

$(P String mixins (not to be confused with
$(DDLINK spec/template-mixin, Template Mixins, template mixins))
enable string constants to be compiled as regular D code
and inserted into the program.
Combining this with compile time manipulation of strings
enables the creation of domain-specific languages.
String mixins can be used in place of:)

$(UL
$(LI Declarations)
$(LI Statements)
$(LI Types)
$(LI Expressions)
)

$(P For example, here we can create a template that generates
a struct with the named members:
$(H2 $(LNAME2 declarations, Generating Declarations))

$(P A mixin can be used for a variable declaration:)
---
enum s = "int y;";
mixin(s);
y = 4; // ok, mixin declared y
---

$(P We can generate a declaration `struct Foo { int bar; }`
using string constants for the identifiers:
)

---
template GenStruct(string Name, string M1)
{
const char[] GenStruct = "struct " ~ Name ~ "{ int " ~ M1 ~ "; }";
}
enum name = "Foo";
enum m1 = "bar";
mixin("struct " ~ name ~
"{ int " ~ m1 ~ "; }");

enum f = Foo(2);
static assert(f.bar == 2);
---
$(P This can be encapsulated in an enum template for reuse:)
---
enum string genStruct(string name, string m1) =
"struct " ~ name ~ "{ int " ~ m1 ~ "; }";

mixin(genStruct!("Foo", "bar"));
---

$(H2 $(LNAME2 multiple-args, Concatenation of Multiple Arguments))

mixin(GenStruct!("Foo", "bar"));
$(P `mixin()` can also concatenate multiple arguments, converting any
non-strings to strings:)
---
$(P which generates:)
enum name = "Foo";
enum m1 = "bar";
$(CODE_HIGHLIGHT enum val = 4;)
mixin("struct ", name,
"{ int ", m1, " = ", $(CODE_HIGHLIGHT val), "; }");

enum f = Foo();
static assert(f.bar == 4);
---
struct Foo { int bar; }

$(H2 $(LNAME2 statements, Generating Statements))

---
int x = 3;
mixin("
foreach (i; 0 .. 3)
writeln(x + i);
");
---
$(P This outputs:)
$(CONSOLE
3
4
5
)
$(P See also: $(GLINK2 statement, MixinStatement).)

$(H2 $(LNAME2 types, Generating Types))

---
mixin("int")* p; // int* p
mixin("int")[] a; // int[] a;
mixin("int[]") b; // int[] b;
---

$(H2 $(LNAME2 expressions, Generating Expressions))

---
int foo(int x)
{
return mixin("x +", 1) * 7; // same as `return (x + 1) * 7`
}
---

$(H2 $(LNAME2 dsl, Domain Specific Languages))

$(P
Combining compile-time function execution with `mixin()`
enables the use of domain-specific languages:)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of this example. JavaScript is a general purpose programming language, a DSL is something like regex or SQL. Implementing convertJS would be a huge undertaking, and it doesn't reflect a real use case (or at least I don't see it). Why would you write this, instead of translating the JS offline and putting the result in your .d file?

I suggest using pegged as an example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why any of that really matters. If the reader can't discern "real use cases" from a demonstrative example then they're not going to get anything from any example.

Besides, JavaScript did literally start as the ultimate domain specific language a long time ago.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mixins are a confusing feature, and their working / purpose are easily misunderstood. Consider the target audience for these articles: they're programmers who are new to D and might not have done any meta-programming beforehand. When you tell "mixin("int y;") is equivalent to int y;" the first question is "what does mixin do then?".

The specification can be very dry in its explanations, but the article should help programmers understand what actual problems the feature solves. There's a tendency of new D users to complicate things with mixin when regular functions could be used, which is not helped by poor official examples.

---
mixin(convertJS(q{
var msg = "Hello world!"
console.log(msg)
msg = 5
console.log(msg)
}));
---
$(P `convertJS` would generate a string that would be compiled
to the following D code:)
---
import std.variant;

Variant msg = "Hello world!";
writeln(msg);
msg = 5;
writeln(msg);
---

$(NOTE Text editors can syntax highlight the contents of `q{}`
strings as D tokens rather than a string, which is useful with
`mixin()`. The above JS code happens to be token compatible
with D.)

$(H2 $(LNAME2 cpp, Comparison with C preprocessor))

$(P Superficially, since D mixins can manipulate text and compile
the result, it has some similar properties to the C preprocessor.
Expand Down Expand Up @@ -61,7 +160,7 @@ END

This monkey business is impossible with mixins.
Mixed in text must form complete declarations,
statements, or expressions.
statements, types, or expressions.
)

$(LI C macros will affect everything following that has
Expand Down Expand Up @@ -93,3 +192,4 @@ END
Macros:
TITLE=String Mixins
SUBNAV=$(SUBNAV_ARTICLES)
NOTE= $(DIVC note, $(B Note:) $0)
7 changes: 7 additions & 0 deletions spec/module.dd
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,13 @@ $(GNAME MixinDeclaration):
$(GLINK DeclDefs), and is compiled as such.
)

$(SPEC_RUNNABLE_EXAMPLE_COMPILE
---
enum s = "int y;";
mixin(s);
y = 4; // ok, mixin declared y
---
)

$(H2 $(LEGACY_LNAME2 PackageModule, package-module, Package Module))

Expand Down
26 changes: 11 additions & 15 deletions spec/statement.dd
Original file line number Diff line number Diff line change
Expand Up @@ -1999,30 +1999,26 @@ $(GNAME MixinStatement):
)

$(SPEC_RUNNABLE_EXAMPLE_RUN
---
import std.stdio;

void main()
{
int i = 0;
---
int x = 3;
mixin("
int x = 3;
for (; i < 3; i++)
writeln(x + i, i);
foreach (i; 0 .. 3)
writeln(x + i);
"); // ok
---
)

enum s = "int y;";
mixin(s); // ok
y = 4; // ok, mixin declared y
$(SPEC_RUNNABLE_EXAMPLE_COMPILE
---
int y;

string t = "y = 3;";
//mixin(t); // error, t is not evaluatable at compile time
//mixin("y =") 4; // error, string must be complete statement

//mixin("y =") 4; // error, string must be complete statement
mixin("y =" ~ "4;"); // ok
mixin("y =", 2+2, ";"); // ok
}
---
---
)

$(SPEC_SUBNAV_PREV_NEXT expression, Expressions, arrays, Arrays)
Expand Down