www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - A couple of extensions to `with` that would be worthwhile

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
We have an awkward idiom in the dmd source code:

https://github.com/dlang/dmd/search?q=extendedPathThen
https://github.com/dlang/dmd/search?q=toWStringzThen

It's a tail wagging the proverbial dog. Clearly we could do with better 
ways. We can improve the `with` statement as follows:

1. Accept named `with`:

with (auto xpath = extendPath(name)) {
     // the only name injected here is xpath
}

All destructors for the expression involved in `with` are called AFTER 
the with is done.

2. Accept primitive types in named `with`:

with (auto xpath = extendPath(name).str) {
     // the only name injected here is xpath
     // assume str has type wstring, then xpath
     // also has type string
}

Again, the destructor of the temporary object created is called AFTER 
the with statement ends, even though the object is only used as a temporary.

3. Accept `with` with colon:

with (auto xpath = extendPath(name).str):

The meaning is like a scoped `with` that extends though the end of the 
scope.

4. Accept `with` at top level.

All `with` forms that take a type should be accepted at top level. This 
allows entire modules or fragments thereof to look names up in a 
flexible manner.
Oct 13 2021
next sibling parent reply MrSmith <mrsmith33 yandex.ru> writes:
On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei 
Alexandrescu wrote:
 3. Accept `with` with colon:

 with (auto xpath = extendPath(name).str):

 The meaning is like a scoped `with` that extends though the end 
 of the scope.
Isn't this equivalent to: ```d auto xpath = extendPath(name).str; ```
Oct 13 2021
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.com> writes:
On 2021-10-13 8:45, MrSmith wrote:
 On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei Alexandrescu wrote:
 3. Accept `with` with colon:

 with (auto xpath = extendPath(name).str):

 The meaning is like a scoped `with` that extends though the end of the 
 scope.
Isn't this equivalent to: ```d auto xpath = extendPath(name).str; ```
Sadly no: https://run.dlang.io/is/Ja1uVk
Oct 13 2021
prev sibling next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei 
Alexandrescu wrote:
 1. Accept named `with`:

 with (auto xpath = extendPath(name)) {
     // the only name injected here is xpath
 }
I've wanted this before, but not the "only name injected" thing, since that defeats the whole point of `with`. I'd want it to be a standard with statement, just with a name given for the overall thing so you can refer to it as a whole in that scope too.
 with (auto xpath = extendPath(name).str) {
     // the only name injected here is xpath
     // assume str has type wstring, then xpath
     // also has type string
 }
What's the difference between this and `if(auto xpath = ...)` ? It wouldn't be null anyway. Or between it and --- { auto xpath = ....; // stuff } --- ? That's why I think the with needs to keep its with behavior even if you give it a name.
Oct 13 2021
next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Wednesday, 13 October 2021 at 13:05:06 UTC, Adam D Ruppe wrote:
 On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei [...]
 with (auto xpath = extendPath(name).str) {
     // the only name injected here is xpath
     // assume str has type wstring, then xpath
     // also has type string
 }
What's the difference between this and `if(auto xpath = ...)` ? It wouldn't be null anyway. Or between it and { auto xpath = ....; // stuff } ?
I think that the point of proposal 1 is to have something like, after lowering to common AST constructs: ```d { { auto xpath = ....; scope (exit) destroy(xpath) // stuff } // destroy(xpath) really happens here } ```
Oct 13 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 13 October 2021 at 13:16:36 UTC, Basile B. wrote:
 I think that the point of proposal 1 is to have something like, 
 after lowering to common AST constructs:

 ```d
 {
   {
     auto xpath = ....;
     scope (exit) destroy(xpath)
     // stuff
   }
   // destroy(xpath) really happens here
 }
 ```
Destructors are already called automatically at the end of a block scope.
Oct 13 2021
next sibling parent Basile B. <b2.temp gmx.com> writes:
On Wednesday, 13 October 2021 at 13:20:02 UTC, Paul Backus wrote:
 On Wednesday, 13 October 2021 at 13:16:36 UTC, Basile B. wrote:
 I think that the point of proposal 1 is to have something 
 like, after lowering to common AST constructs:

 ```d
 {
   {
     auto xpath = ....;
     scope (exit) destroy(xpath)
     // stuff
   }
   // destroy(xpath) really happens here
 }
 ```
Destructors are already called automatically at the end of a block scope.
so proposal 1. is definitively not useful.
Oct 13 2021
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.com> writes:
On 2021-10-13 9:20, Paul Backus wrote:
 On Wednesday, 13 October 2021 at 13:16:36 UTC, Basile B. wrote:
 I think that the point of proposal 1 is to have something like, after 
 lowering to common AST constructs:

 ```d
 {
   {
     auto xpath = ....;
     scope (exit) destroy(xpath)
     // stuff
   }
   // destroy(xpath) really happens here
 }
 ```
Destructors are already called automatically at the end of a block scope.
The point is that with keeps the entire expression live, even if you only care about a part of it: struct S { int x; ... } with (auto y = S().x) { ... } In this case the S() temporary will be destroyed only at the end of with's scope. This enables idioms such as "I want to create a temporary string and mess with it".
Oct 13 2021
next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Wednesday, 13 October 2021 at 19:18:55 UTC, Andrei 
Alexandrescu wrote:
 The point is that with keeps the entire expression live, even 
 if you only care about a part of it:
OK, that explains things now. I see what you're going for. Not in love with it being `with` that works differently than the other `with`s but it kinda makes sense anyway. The lambda pattern though does have a few other advantages... I think it is generally easier to use... but gotta chew on it a bit.
Oct 13 2021
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.com> writes:
On 2021-10-13 15:28, Adam D Ruppe wrote:
 On Wednesday, 13 October 2021 at 19:18:55 UTC, Andrei Alexandrescu wrote:
 The point is that with keeps the entire expression live, even if you 
 only care about a part of it:
OK, that explains things now. I see what you're going for. Not in love with it being `with` that works differently than the other `with`s but it kinda makes sense anyway. The lambda pattern though does have a few other advantages... I think it is generally easier to use... but gotta chew on it a bit.
One major issue with the lambda pattern is composition. Every layer of composition introduces yet another scope AND parenthesis level. That doesn't scale well at all.
Oct 13 2021
parent reply Mathias LANG <geod24 gmail.com> writes:
On Wednesday, 13 October 2021 at 19:30:43 UTC, Andrei 
Alexandrescu wrote:
 On 2021-10-13 15:28, Adam D Ruppe wrote:
 On Wednesday, 13 October 2021 at 19:18:55 UTC, Andrei 
 Alexandrescu wrote:
 The point is that with keeps the entire expression live, even 
 if you only care about a part of it:
OK, that explains things now. I see what you're going for. Not in love with it being `with` that works differently than the other `with`s but it kinda makes sense anyway. The lambda pattern though does have a few other advantages... I think it is generally easier to use... but gotta chew on it a bit.
One major issue with the lambda pattern is composition. Every layer of composition introduces yet another scope AND parenthesis level. That doesn't scale well at all.
The lack of composability is annoying, but how often is it practically a problem ? Those functions are used for D to C string conversion, so in practice they are always at the end of the pipeline (just like `each` is). I don't really see how those could justify a new language feature.
Oct 13 2021
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 10/13/21 10:30 PM, Mathias LANG wrote:
 On Wednesday, 13 October 2021 at 19:30:43 UTC, Andrei Alexandrescu wrote:
 On 2021-10-13 15:28, Adam D Ruppe wrote:
 On Wednesday, 13 October 2021 at 19:18:55 UTC, Andrei Alexandrescu 
 wrote:
 The point is that with keeps the entire expression live, even if you 
 only care about a part of it:
OK, that explains things now. I see what you're going for. Not in love with it being `with` that works differently than the other `with`s but it kinda makes sense anyway. The lambda pattern though does have a few other advantages... I think it is generally easier to use... but gotta chew on it a bit.
One major issue with the lambda pattern is composition. Every layer of composition introduces yet another scope AND parenthesis level. That doesn't scale well at all.
The lack of composability is annoying, but how often is it practically a problem ? Those functions are used for D to C string conversion, so in practice they are always at the end of the pipeline (just like `each` is). I don't really see how those could justify a new language feature.
Well extendedPathThen uses toStringz :o).
Oct 13 2021
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 13 October 2021 at 19:18:55 UTC, Andrei 
Alexandrescu wrote:
 The point is that with keeps the entire expression live, even 
 if you only care about a part of it:

 struct S { int x; ... }

 with (auto y = S().x)
 {
    ...
 }

 In this case the S() temporary will be destroyed only at the 
 end of with's scope.
Not a fan. It uses the same syntax as `if (auto y = S().x)` but has subtly different semantics, in a way that's likely to go unnoticed in common usage. It's also not obvious which sub-expressions get kept alive. For example: // What's kept alive--S(1), S(2), or both? bool b = /* ... */; with (auto y = (b ? S(1) : S(2)).x) // Are S(1) and S(2) kept alive? S fun(S a, S b) { /* ... */ } with (auto y = fun(S(1), S(2)).x) Of course one could work out a set of rules for this and add them to the language spec, but it seems like a lot of effort and complexity for not much benefit, especially when we can already get the desired behavior with normal block scopes: { S temporary = S(); with(auto y = temporary.x) { /* ... */ } } // temporary's lifetime ends here
 This enables idioms such as "I want to create a temporary 
 string and mess with it".
You can already do this by wrapping the string in a struct that owns its memory: struct OwnedString { string[] payload; ~this() { free(payload.ptr); } // etc. } That way, D's existing scoping rules will ensure that the resources are released at the correct time, and you do not have to worry about keeping some unrelated temporary alive via obscure special-case language rules.
Oct 13 2021
next sibling parent deadalnix <deadalnix gmail.com> writes:
On Wednesday, 13 October 2021 at 20:41:11 UTC, Paul Backus wrote:
 Of course one could work out a set of rules for this and add 
 them to the language spec, but it seems like a lot of effort 
 and complexity for not much benefit, especially when we can 
 already get the desired behavior with normal block scopes:
This. If the concern is that the temporary's destruction will destroy the variable, it's a general problem that is tackled by ownership.
Oct 13 2021
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 10/13/21 4:41 PM, Paul Backus wrote:
 On Wednesday, 13 October 2021 at 19:18:55 UTC, Andrei Alexandrescu wrote:
 The point is that with keeps the entire expression live, even if you 
 only care about a part of it:

 struct S { int x; ... }

 with (auto y = S().x)
 {
    ...
 }

 In this case the S() temporary will be destroyed only at the end of 
 with's scope.
Not a fan. It uses the same syntax as `if (auto y = S().x)` but has subtly different semantics, in a way that's likely to go unnoticed in common usage.
Yah, good point. Prolly it would be good to find a reasonable way to extend the lifetime of the temporary in a more general case.
Oct 13 2021
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.com> writes:
On 2021-10-13 9:05, Adam D Ruppe wrote:
 On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei Alexandrescu wrote:
 1. Accept named `with`:

 with (auto xpath = extendPath(name)) {
     // the only name injected here is xpath
 }
I've wanted this before, but not the "only name injected" thing, since that defeats the whole point of `with`. I'd want it to be a standard with statement, just with a name given for the overall thing so you can refer to it as a whole in that scope too.
 with (auto xpath = extendPath(name).str) {
     // the only name injected here is xpath
     // assume str has type wstring, then xpath
     // also has type string
 }
What's the difference between this and `if(auto xpath = ...)` ? It wouldn't be null anyway.
Not generalizable.
 Or between it and
 
 ---
 {
    auto xpath = ....;
    // stuff
 }
 ---
 
 ?
In cases such as toStringz we don't care about the temporary (it's just a shell meant to ensure resource reclamation), we only care about using the slice within.
 That's why I think the with needs to keep its with behavior even if you 
 give it a name.
That would mess things up.
Oct 13 2021
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei 
Alexandrescu wrote:
 We have an awkward idiom in the dmd source code:

 https://github.com/dlang/dmd/search?q=extendedPathThen
 https://github.com/dlang/dmd/search?q=toWStringzThen

 It's a tail wagging the proverbial dog. Clearly we could do 
 with better ways. We can improve the `with` statement as 
 follows:

 1. Accept named `with`:

 with (auto xpath = extendPath(name)) {
     // the only name injected here is xpath
 }

 All destructors for the expression involved in `with` are 
 called AFTER the with is done.
Isn't this the same thing as a normal block scope? { auto xpath = extendPath(name); // the name xpath is available here // ... } // xpath is destroyed here As far as I can tell the reason a block scope isn't used in DMD is that these methods don't use destructors for cleanup, they use manual memory management. So maybe that's the real source of the awkwardness.
 3. Accept `with` with colon:

 with (auto xpath = extendPath(name).str):

 The meaning is like a scoped `with` that extends though the end 
 of the scope.
This would also be useful for types like ddash's Expect [1], which define static factory methods for each case of a sum type. The example in the linked documentation could be rewritten as: Expect!int toInt(string str) { with(typeof(return)): import std.conv: to; try { return expected(str.to!int); } catch (Exception ex) { return unexpected(ex.msg); } } ...where the `with` statement is used to bring `expected` and `unexpected` into scope, and the colon avoids introducing an extra level of nesting. [1] https://aliak00.github.io/ddash/ddash/utils/expect/Expect.html
 4. Accept `with` at top level.

 All `with` forms that take a type should be accepted at top 
 level. This allows entire modules or fragments thereof to look 
 names up in a flexible manner.
Seems like a natural "turtles all the way down" extension of the above.
Oct 13 2021
prev sibling next sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei 
Alexandrescu wrote:
 It's a tail wagging the proverbial dog. Clearly we could do 
 with better ways. We can improve the `with` statement as 
 follows:
I prefer with() while using the arsd.simpledisplay because it's needed a scope. However, when you use double with(), an issue to come up! Compiled as follows, my only solution is: ```d Painter window; with(Color) window = new Painter("AxialPCB", black, gray, red); with(window) { //... } window.Handlers.eventLoop(0); ``` The new ideas are great about with() like the same inline import! If I used this way, it can't access it from the outside: ```d with(new Painter("AxialPCB", Color.black, Color.gray, Color.red) { //*/ } ```
Oct 13 2021
prev sibling next sibling parent Andre Pany <andre s-e-a-p.de> writes:
On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei 
Alexandrescu wrote:
 We have an awkward idiom in the dmd source code:

 https://github.com/dlang/dmd/search?q=extendedPathThen
 https://github.com/dlang/dmd/search?q=toWStringzThen

 [...]
https://issues.dlang.org/show_bug.cgi?id=13526 Kind regards Andre
Oct 13 2021
prev sibling next sibling parent max haughton <maxhaton gmail.com> writes:
On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei 
Alexandrescu wrote:
 We have an awkward idiom in the dmd source code:

 https://github.com/dlang/dmd/search?q=extendedPathThen
 https://github.com/dlang/dmd/search?q=toWStringzThen

 [...]
We could also have a trailing with to allow Haskell style blah where blah = xyz code.
Oct 13 2021
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/13/21 1:13 PM, Andrei Alexandrescu wrote:
 We can improve the `with` statement as follows:
I don't think this is an "improvement" of the `with` statement. It's a completely novel feature that happens to be syntactically very close to the existing `with` statement. There's an obvious meaning for the new syntax as an extension of the existing feature, but it is not what you propose. Not great. Maybe there is a better way to address this problem?
Oct 13 2021
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 10/13/21 4:13 PM, Timon Gehr wrote:
 On 10/13/21 1:13 PM, Andrei Alexandrescu wrote:
 We can improve the `with` statement as follows:
I don't think this is an "improvement" of the `with` statement. It's a completely novel feature that happens to be syntactically very close to the existing `with` statement. There's an obvious meaning for the new syntax as an extension of the existing feature, but it is not what you propose. Not great. Maybe there is a better way to address this problem?
You're right. The `while (type)` and `while (type):` at top level is probably good to salvage.
Oct 13 2021
parent reply Tejas <notrealemail gmail.com> writes:
On Thursday, 14 October 2021 at 02:58:32 UTC, Andrei Alexandrescu 
wrote:
 On 10/13/21 4:13 PM, Timon Gehr wrote:
 On 10/13/21 1:13 PM, Andrei Alexandrescu wrote:
 We can improve the `with` statement as follows:
I don't think this is an "improvement" of the `with` statement. It's a completely novel feature that happens to be syntactically very close to the existing `with` statement. There's an obvious meaning for the new syntax as an extension of the existing feature, but it is not what you propose. Not great. Maybe there is a better way to address this problem?
You're right. The `while (type)` and `while (type):` at top level is probably good to salvage.
Maybe having the syntax as: ```d with <construct> as <identifier>{ //do stuff } ``` Would be better? It could also be useful in `Python` style management for `file` handles, plus be visually distinct enough from other uses of `with` to not simply be overlooked.
Oct 14 2021
next sibling parent reply jfondren <julian.fondren gmail.com> writes:
On Thursday, 14 October 2021 at 07:12:29 UTC, Tejas wrote:
 Maybe having the syntax as:
 ```d
 with <construct> as <identifier>{
     //do stuff
 }
 ```
 Would be better?
This is completely novel syntax (for d), when the proposed syntax is used all over the place in d. How are you going to explain this to Scott Meyers, after his whole speech about "please don't make the decisions that require professional explainers like me"? https://www.youtube.com/watch?v=KAWA1DuvCnQ
 It could also be useful in `Python` style management for `file` 
 handles
It's not as nice but `with` and a scope both offer this (with a strace confirming that the file is closed before the writeln): ```d void main() { import std.stdio : File, writeln, stdout; with (File("/etc/passwd")) { stdout.write(readln); // std.stdio.write is shadowed by File } writeln("that's enough"); } void main() { import std.stdio : File, writeln, write; { auto file = File("/etc/passwd"); // at least there's no extra nesting for multiple files? write(file.readln); } writeln("that's enough"); } ```
 , plus be visually distinct enough from other uses of `with` to 
 not simply be overlooked.
but if we're adding syntax, how about something to make tuple deconstruction a little bit nicer? `with` already offers that but nobody's noticed in the other thread about what they'd like in d, because of how much work it is: ```d import std.stdio : writeln; import std.typecons : Tuple; unittest { struct S { int a; float b; } with (S(1, 2)) { writeln(a); writeln(b); } } auto opaque() { struct S { char a; ubyte b; } return S('a', 'b'); } unittest { with (Tuple!(char, "x", ubyte, "y")(opaque().tupleof)) { writeln(x); writeln(cast(char) y); } } ``` the proposed `with():` would get rid of the nesting that's not wanted for tuple deconstruction, but naming could still be improved. How about borrowing from foreach? ```d unittest { with (char x, ubyte y; opaque): // implicit .tupleof writeln(x); writeln(cast(char) y); } ```
Oct 14 2021
parent Tejas <notrealemail gmail.com> writes:
On Thursday, 14 October 2021 at 08:37:40 UTC, jfondren wrote:
 On Thursday, 14 October 2021 at 07:12:29 UTC, Tejas wrote:
 [...]
This is completely novel syntax (for d), when the proposed syntax is used all over the place in d. How are you going to explain this to Scott Meyers, after his whole speech about "please don't make the decisions that require professional explainers like me"? https://www.youtube.com/watch?v=KAWA1DuvCnQ [...]
Timon's tuple DIP should've been worked on further and accepted ;(
Oct 14 2021
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 14 October 2021 at 07:12:29 UTC, Tejas wrote:
 Maybe having the syntax as:
 ```d
 with <construct> as <identifier>{
     //do stuff
 }
 ```
 Would be better?
 It could also be useful in `Python` style management for `file` 
 handles, plus be visually distinct enough from other uses of 
 `with` to not simply be overlooked.
In D this is accomplished using a regular block scope and a variable declaration: ```d { auto <identifier> = <construct>; // do stuff } ``` Python needs special syntax for it because Python doesn't have block scopes--the only thing that introduces a new scope in Python is a function.
Oct 14 2021
parent reply Tejas <notrealemail gmail.com> writes:
On Thursday, 14 October 2021 at 13:44:24 UTC, Paul Backus wrote:
 On Thursday, 14 October 2021 at 07:12:29 UTC, Tejas wrote:
 Maybe having the syntax as:
 ```d
 with <construct> as <identifier>{
     //do stuff
 }
 ```
 Would be better?
 It could also be useful in `Python` style management for 
 `file` handles, plus be visually distinct enough from other 
 uses of `with` to not simply be overlooked.
In D this is accomplished using a regular block scope and a variable declaration: ```d { auto <identifier> = <construct>; // do stuff } ``` Python needs special syntax for it because Python doesn't have block scopes--the only thing that introduces a new scope in Python is a function.
So is it safe to say what Andrei wants can be accomplished with new block + `with`? ```d void main(){ int a; struct S{ int d;} { S s; with(s){ a = d; } } } ``` I kinda remembered that Python can't introduce new scope arbitrarily when jfondren criticized my opinion as well. Is this proposed extension merely syntax sugar, then?
Oct 14 2021
parent Paul Backus <snarwin gmail.com> writes:
On Thursday, 14 October 2021 at 13:50:29 UTC, Tejas wrote:
 So is it safe to say what Andrei wants can be accomplished with 
 new block + `with`?
Yes.
 Is this proposed extension merely syntax sugar, then?
Yes, but most syntax is syntax sugar. The main issue with it is that it's inconsistent with other, similar syntax sugar that's already in the language.
Oct 14 2021
prev sibling parent reply Antonio <antonio abrevia.net> writes:
On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei 
Alexandrescu wrote:
 We have an awkward idiom in the dmd source code:

 https://github.com/dlang/dmd/search?q=extendedPathThen
 https://github.com/dlang/dmd/search?q=toWStringzThen

 It's a tail wagging the proverbial dog. Clearly we could do 
 with better ways. We can improve the `with` statement as 
 follows:

 1. Accept named `with`:

 with (auto xpath = extendPath(name)) {
     // the only name injected here is xpath
 }

 All destructors for the expression involved in `with` are 
 called AFTER the with is done.
- I usually take the functional way: ```d (string xpath){ ... }( expandPath(name) ); ``` And UFCS doesn't help to "beutify" the code. This doesn't work (And, in my opinion, should): ```d expandPath(name).(auto xpath){ ... }; ``` This neither ```d expandPath(name).((auto xpath){ ... })(); ``` May be "with" could be an alternative
Oct 14 2021
parent Paul Backus <snarwin gmail.com> writes:
On Thursday, 14 October 2021 at 08:44:27 UTC, Antonio wrote:
 - I usually take the functional way:

 ```d
 (string xpath){		
   ...
 }( expandPath(name) );
 ```	

 And UFCS doesn't help to "beutify" the code.  This doesn't work 
 (And, in my opinion, should):

 ```d
 expandPath(name).(auto xpath){		
   ...
 };
 ```
 This neither

 ```d
 expandPath(name).((auto xpath){		
   ...
 })();
 ```

 May be "with" could be an alternative
I sometimes use [`std.functional.pipe`][1] for this: ```d expandPath(name).pipe!((auto xpath) { /* ... */ }); ``` [1]: https://phobos.dpldocs.info/std.functional.pipe.html
Oct 14 2021