www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Is it possible to use templates to implement something like the

reply WraithGlade <anonymous noreply.com> writes:
Hello everyone! This is my first post in these forums.

D is an interesting language (basically a saner C++, which is a 
nice prospect because I've used C++ working in the game industry 
in the past) and I've been considering using it and may still do 
so.

However, there is a very basic debugging feature that I've always 
thought all programming languages should have (because it is so 
useful and yet also trivial to implement and inherently harmless) 
but which a surprisingly large number of languages don't support:

Specifically, I want a way to create a print command that behaves 
like ` show` from Julia lang or `dump` from (if my memory is 
correct) Nim.

Basically, I want there to be a way to print both an expression 
and its value but to only have to write the expression once 
(which also aids refactoring). Such a feature is extremely useful 
for faster print-based debugging.

Thus, instead of having to write something like this:

```
writeln("1 + 2 == ", 1 + 2);
```

I want to just be able to write this:

```
show!(1 + 2)
```

... and here is the closest I've been able to get in D, after 
roughly a dozen attempts:

```
import std.stdio;

void show(alias expr)()
{
   writeln(expr.stringof, " == ", expr);

   //https://ddili.org/ders/d.en/templates_more.html
   //says that `alias` allows "any expression" but in
   //reality the expression gets evaluated and hence
   //information about what it really was is lost.
}

void main()
{
   show!(1 + 2);
   //I want this to print "1 + 2 == 3",
   //but instead it prints "3 == 3"

   const x = 1 + 2;
   show!(x);
   //This at least prints "x == 3" though.
}
```

See the comments in the above code for info on what it does 
versus what I want it to do.

Usually I prefer `assert` whenever I actually know what the 
values of something are, but there are many cases where I don't 
know what the values are and want to see easily.

**This feature is a great litmus test for how expressive a 
language actually is.** Indeed, it is one of the best simple 
litmus tests for programming language design that I've found. It 
is trivial yet speaks volumes of the thought that has gone into 
the design of any language.

Even $5 solar-powered desktop calculators often leave the 
expression of what you write *still visible* after you've 
computed it, yet many programming languages don't even provide a 
way of eliminating this extremely common form of redundancy and 
require duplicated typing in strings repeatedly.

One would think that more programming languages would account for 
this when you consider that much of the *whole purpose* of 
programming languages is enabling the user to get computed values 
evaluated and displayed as easily and pragmatically as 
possible... yet so many languages can't even do the above despite 
it being one of the most obvious ergonomic factors one could ever 
have for anything that is used for computation. That is why the 
absence of it in any language annoys me so much. The lack of it 
is an absurdity, essentially, if you think about it from first 
principles and user experience.

On the other hand, I am aware that macros make control flow 
harder to follow, but to counter that: String-generating macros 
that aid debugging are an inherently harmless subset of macros. 
They only make it easier to debug and display diagnostics. 
There's nothing about the `show` macro (or similar 
passive-information constructs) conceptually that harms 
comprehensibility of code.

I've for years thought it would be nice to have a language that 
lacks the more extreme macros but still has the ability to 
implement things like the `show` macro.

Is D such a language? Is there any way to implement what I want 
(as above)?

The frustration with trying to get this working makes me consider 
just using Nim instead, which has extremely flexible macros and 
can do this easily.

I personally believe that all languages should have this, even if 
they have to make a one-time exception to allow just the `show` 
macro but not other macros.

Why should merely displaying the correspondence between 
expressions and their outputs be hard or impossible in so many 
languages? Think about it. It is bizarre that it is so often 
absent.

If D can't do this currently then I suggest that it be made to be 
capable of it. This kind of macro is not like other macros. 
Debugging and diagnostic pragmatism doesn't confound program 
logic, unlike how esoteric macros often do.

Anyway, thank you for reading and sorry for my verbosity. This is 
one of my biggest pet peeves regarding programming language 
design.
Sep 08
next sibling parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Sunday, 8 September 2024 at 22:01:10 UTC, WraithGlade wrote:
 I want to just be able to write this:

 ```
 show!(1 + 2)
 ```
```d void show(string s)(){ auto res=mixin(s); writeln(s,"==",res); } show!"1+2"; ```
Sep 08
parent Paul Backus <snarwin gmail.com> writes:
On Sunday, 8 September 2024 at 23:01:22 UTC, monkyyy wrote:
 On Sunday, 8 September 2024 at 22:01:10 UTC, WraithGlade wrote:
 I want to just be able to write this:

 ```
 show!(1 + 2)
 ```
```d void show(string s)(){ auto res=mixin(s); writeln(s,"==",res); } show!"1+2"; ```
This works for `show!"1+2"`, but it won't work for `show!"x"`, because `x` is not visible inside the body of the `show` function.
Sep 08
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 8 September 2024 at 22:01:10 UTC, WraithGlade wrote:
 Basically, I want there to be a way to print both an expression 
 and its value but to only have to write the expression once 
 (which also aids refactoring). Such a feature is extremely 
 useful for faster print-based debugging.

 Thus, instead of having to write something like this:

 ```
 writeln("1 + 2 == ", 1 + 2);
 ```

 I want to just be able to write this:

 ```
 show!(1 + 2)
 ```
The only way to write a macro in D that executes code in the scope where the macro is *invoked* (rather than the scope where it's *defined*) is to use a `mixin`: ```d enum show(string expr) = `writeln(q"(` ~ expr ~ `)", " == ", ` ~ expr ~ `);`; void main() { import std.stdio; mixin(show!"1 + 2"); // 1 + 2 == 3 const int x = 1 + 2; mixin(show!"x"); // x == 3 } ```
Sep 08
parent monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 9 September 2024 at 00:34:03 UTC, Paul Backus wrote:
 
 The only way
is that a challenge?
 void main()
 {
     import std.stdio;

     mixin(show!"1 + 2"); // 1 + 2 == 3

     const int x = 1 + 2;
     mixin(show!"x"); // x == 3
 }
 ```
idk why you calling it a macro, im pretty sure d isnt going to make it easy to climb up the ast ever, and op wants sometin that reads statement literals.
Sep 08
prev sibling next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Sunday, 8 September 2024 at 22:01:10 UTC, WraithGlade wrote:
 Basically, I want there to be a way to print both an expression 
 and its value but to only have to write the expression once 
 (which also aids refactoring). Such a feature is extremely 
 useful for faster print-based debugging.
 [...]
 I want to just be able to write this:

 ```
 show!(1 + 2)
 ```
Try this: ``` import core.interpolation; void show(Args...)(InterpolationHeader hdr, Args args, InterpolationFooter ftr) { import std.stdio; foreach(arg; args) { static if(is(typeof(arg) == InterpolatedExpression!code, string code)) write(code); else static if(is(typeof(arg) == InterpolatedLiteral!str, string str)) write(str); else write(" = ", arg); } writeln(); } ``` Used like: ``` void main() { int a = 5, b = 22; show(i`$(a), $(b), $(a + b)`); } ``` Output: a = 5, b = 22, a + b = 27 You can show as many expressions as you want in there, each $(....) thing is expanded to `code inside = result`. So show(i"$(1 + 2)"); // prints 1 + 2 = 3 The interpolated sequence syntax makes it a bit heavier: that `i"$( .... )"` around it is required here, but it works anyway.
Sep 09
parent reply WraithGlade <anonymous noreply.com> writes:
Thank you all for your very helpful replies and for taking the 
time to make them!

It is indeed heartening to see that D supports multiple different 
forms of this mechanism.

This is certainly sufficient for the example I gave.

I suggest that something like this should be added to the 
standard library and perhaps even shown in early examples in the 
tutorial and documentation. Such things are so routine that I 
would argue that it should be the most common form of print-based 
debugging. It also seems to be becoming an expected/standard 
feature among new languages too, for obvious practical reasons.

The prospect of using D does look brighter now, knowing all this.

----

More specifically, I am planning to do a combination of general 
application development, game development, and eventually 
building a language transpiler of my own (for which I've been 
gradually coming up with ideas for over the years). I have grown 
tired of dealing with C and C++'s mess and build ecosystem.

D's expressiveness is adequate now that I know what you've all 
said here.

I am debating between D, Nim, Zig, and Rust. Each have pros and 
cons, although all are clearly great and none would be "bad" 
choices (barring unexpected events).

I will consider it for a while longer. I want a stable platform 
to build on long-term that is also flexible and pleasant to work 
with.

So, tangentially, I'd also be interested in hearing any of your 
all's thoughts about that regarding D (and/or the other 
prospective languages).

Thanks again for reading and for the fast and extremely helpful 
replies! It is much appreciated! I wish you all a wonderful 
day/night/week/etc!
Sep 09
parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 9 September 2024 at 17:39:46 UTC, WraithGlade wrote:
 
import std; auto parse(char[] s)=>s[9..$-2]; void show(T,string file= __FILE__,int line=__LINE__)(T t){ writeln(File(file).byLine.drop(line-1).front.parse," == ",t); } void main(){ int i=3; show(i++ + ++i * i); show(i); }
Sep 09
next sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
On Monday, 9 September 2024 at 17:56:04 UTC, monkyyy wrote:
 auto parse(char[] s)=>s[9..$-2];
 void show(T,string file= __FILE__,int line=__LINE__)(T t){
     writeln(File(file).byLine.drop(line-1).front.parse," == 
 ",t);
 }
 void main(){
     int i=3;
     show(i++ + ++i * i);
     show(i);
 }
This solution is really successful. I didn't think there could be any other solution than mixin(); this is utterly ingenious! There is a working version of the code below and different tests. My only concern is why i == 5 in the line below when i == 28? ```d import std; auto parse(char[] s) => s[9..$ - 2]; void show(string file = __FILE__, int line = __LINE__, T)(T t) { File(file).byLine .drop(line-1) .front .parse .writeln(" == ", t); } template f(ulong n) { static if(n < 2) const f = 1; else const f = n * f!(n - 1); } void main() { int i = 3; show(i++ + ++i * i); show(i); T factorial(T)(T n)=>n<2?1:n*factorial(--n); show(factorial(10)); show(f!10); } /* Prints: i++ + ++i * i == 28 i == 5 factorial(10) == 3628800 f!10 == 3628800 */ ``` SDB 79
Sep 09
parent monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 9 September 2024 at 19:29:01 UTC, Salih Dincer wrote:
 My only concern is why i == 5 in the line below when i == 28?
i isn't being modified by a `*=`. its only the two ++
Sep 09
prev sibling parent reply WraithGlade <anonymous noreply.com> writes:
On Monday, 9 September 2024 at 17:56:04 UTC, monkyyy wrote:
 On Monday, 9 September 2024 at 17:39:46 UTC, WraithGlade wrote:
 
import std; auto parse(char[] s)=>s[9..$-2]; void show(T,string file= __FILE__,int line=__LINE__)(T t){ writeln(File(file).byLine.drop(line-1).front.parse," == ",t); } void main(){ int i=3; show(i++ + ++i * i); show(i); }
I tested this version on my machine and the output isn't correct. It gets cut off. Here's the reformatted and slightly altered version I tested: ``` import std; auto parse(char[] s) => s[9 .. $ - 2]; void show(T, string file = __FILE__, int line = __LINE__)(T t) { File(file) .byLine .drop(line - 1) .front .parse .writeln(" == ", t); } void main(){ int i = 3; show(i++ + ++i * i); show(i); } ``` It outputs this erroneous result: ``` + + ++i * i) == 28 == 5 ``` This is not that surprising considering it uses hardcoded numbers and doesn't look right. Also, moving the built EXE out of the source code directory and into a different folder causes the program to crash when it runs due to not being able to find the source code file. That seems to indicate that this code's file reading doesn't actually happen in compile-time. Maybe there are compiler build options for D that'll write this in in compile time though? In any case, this version seems more brittle than the others at least
Sep 09
next sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 9 September 2024 at 20:52:09 UTC, WraithGlade wrote:
 
 This is not that surprising considering it uses hardcoded 
 numbers and doesn't look right.
I made it on dlang.io which does space tabs, if its not ovisous to a beginner, parse is ~~lazy~~ not correct, it need to do find substring nonsense for real code
 That seems to indicate that this code's file reading doesn't 
 actually happen in compile-time. Maybe there are compiler build 
 options for D that'll write this in in compile time though?
This is one of the old fights, you will have a bad time "ctfe io isnt safe", if your making your own build system `-J=.` and string imports are an option but that will not be aviable for generic code; meaning you cant make something nice for the std.
Sep 09
prev sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Monday, 9 September 2024 at 20:52:09 UTC, WraithGlade wrote:
 In any case, this version seems more brittle than the others at 
 least
After all, you will need it in 2 ways: 1. to learn, 2. to test. After testing, you can disable it with just the version parameter while compiling. It is even possible to turn it on/off by embedding ```// version = logOn; in the code and removing the comment feature. For this, embed VERSION(logOn) in the relevant lines... SDB 79
Sep 09
prev sibling parent reply WB <witold.baryluk gmail.com> writes:
On Sunday, 8 September 2024 at 22:01:10 UTC, WraithGlade wrote:
 Specifically, I want a way to create a print command that 
 behaves like ` show` from Julia lang or `dump` from (if my 
 memory is correct) Nim.
Yes. https://github.com/baryluk/echo/blob/master/echo.d#L205-L209 ```d mixin(echo("echo test: i=$i j=$j Escaping: \\$j, Complicated i+j=${i+j}, End of tests.")); ``` There are other ways of doing similar (using mixing templates for example), but the machinery would be very similar. This code is about 13 years old, but still works. (It is functional and works, but I never it used more than what is in this repo). But now that we have interpolation sequences in the language, it would be way easier, cleaner and more powerful to use them. I have my custom Linux IO library, that utilizes this, and you can do thing like this: ```d Println(i"Ala ma $(ShortHex(123+foo*bar)) $(&m3) $(y.ptr) maybe"); ``` (with zero memory allocations or excessive memory copies).
Sep 11
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Wednesday, 11 September 2024 at 18:29:39 UTC, WB wrote:
 This code is about 13 years old, but still works. (It is 
 functional and works, but I never it used more than what is in 
 this repo).

 But now that we have interpolation sequences in the language, 
 it would be way easier, cleaner and more powerful to use them.
```d assert(echo2("T $i, b, ${i-2}") == "writefln(\"T \", i, \", b, \", (i-2));\n"); ``` It looks clean and understandable. What is not understandable is that it has not been included in std.stdio for over 10 years. I know, string interpolation has just been integrated, but why hasn't something like this been developed in 10 years that would have no side effects and would attract the attention of beginners? SDB 79
Sep 11
parent reply WB <witold.baryluk gmail.com> writes:
On Wednesday, 11 September 2024 at 19:44:54 UTC, Salih Dincer 
wrote:
 It looks clean and understandable. What is not understandable 
 is that it has not been included in std.stdio for over 10 
 years. I know, string interpolation has just been integrated, 
 but why hasn't something like this been developed in 10 years 
 that would have no side effects and would attract the attention 
 of beginners?

 SDB 79
Honestly, D, does not really need it, and most of solutions (like above) do have one or few limitations and drawbacks. Some will be acceptable in some projects, some not. There is plenty of stuff in language and library already, adding small things like that are not really the best thing. There is plenty of very successful languages, that do not offer this (Go, C++, Python 3.5 and earlier, Lua, older JavaScript, etc) and just adding it will not make D automatically more successful. I feel there is too much already in D and standard library, and things are added to quickly and eagerly, and years later we end up in a mess that cannot be solved (because of compatibility). Interpolation strings (added this year) are ok and pretty decent, and well tough in terms of what one can do with them. We still do not know if it is a good or bad design, but we will see. Interpolation string are way more powerful, and cleaner, so it is good half solution like my mixin echo code was not used as a general solution. If you want to use something, just put it in your own project, and it will work to your liking. It does not need to be in a standard library.
Sep 11
next sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Wednesday, 11 September 2024 at 22:06:54 UTC, WB wrote:
 I feel there is too much already in D and standard library, and 
 things are added to quickly and eagerly, and years later we end 
 up in a mess that cannot be solved (because of compatibility).
this isnt autodecoding, cant be, your oping in debuging code and is for temp stuff
Sep 11
prev sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Wednesday, 11 September 2024 at 22:06:54 UTC, WB wrote:
 Honestly, D, does not really need it, and most of solutions 
 (like above) do have one or few limitations and drawbacks. Some 
 will be acceptable in some projects, some not.

 There is plenty of stuff in language and library already, 
 adding small things like that are not really the best thing. 
 There is plenty of very successful languages, that do not offer 
 this (Go, C++, Python 3.5 and earlier, Lua, older JavaScript, 
 etc) and just adding it will not make D automatically more 
 successful. I feel there is too much already in D and standard 
 library, and things are added to quickly and eagerly, and years 
 later we end up in a mess that cannot be solved (because of 
 compatibility).
Yes, I agree; what I see is a very complicated Phobos2. Because it has become like this over time and submodules have been developed to eliminate the confusion. For example, there are things in std.math that we do not use often, a very basic algorithm like gcd() is expected to be written in std.numeric or the factorial() function by you. In short, Phobos3 will adapt better to the language for beginners if it is written systematically from the beginning; std.math should be reorganized from scratch. SDB 79
Sep 12