digitalmars.D - A couple of extensions to `with` that would be worthwhile
- Andrei Alexandrescu (27/27) Oct 13 2021 We have an awkward idiom in the dmd source code:
- MrSmith (6/10) Oct 13 2021 Isn't this equivalent to:
- Andrei Alexandrescu (3/17) Oct 13 2021 Sadly no:
- Adam D Ruppe (19/28) Oct 13 2021 I've wanted this before, but not the "only name injected" thing,
- Basile B. (13/27) Oct 13 2021 I think that the point of proposal 1 is to have something like,
- Paul Backus (3/15) Oct 13 2021 Destructors are already called automatically at the end of a
- Basile B. (2/18) Oct 13 2021 so proposal 1. is definitively not useful.
- Andrei Alexandrescu (12/28) Oct 13 2021 The point is that with keeps the entire expression live, even if you
- Adam D Ruppe (7/9) Oct 13 2021 OK, that explains things now. I see what you're going for.
- Andrei Alexandrescu (4/15) Oct 13 2021 One major issue with the lambda pattern is composition. Every layer of
- Mathias LANG (7/24) Oct 13 2021 The lack of composability is annoying, but how often is it
- Andrei Alexandrescu (2/25) Oct 13 2021 Well extendedPathThen uses toStringz :o).
- Paul Backus (36/47) Oct 13 2021 Not a fan. It uses the same syntax as `if (auto y = S().x)` but
- deadalnix (4/8) Oct 13 2021 This.
- Andrei Alexandrescu (3/20) Oct 13 2021 Yah, good point. Prolly it would be good to find a reasonable way to
- Andrei Alexandrescu (6/39) Oct 13 2021 In cases such as toStringz we don't care about the temporary (it's just
- Paul Backus (30/50) Oct 13 2021 Isn't this the same thing as a normal block scope?
- Salih Dincer (21/24) Oct 13 2021 I prefer with() while using the arsd.simpledisplay because it's
- Andre Pany (6/10) Oct 13 2021 There is also an open issue for #1
- max haughton (4/8) Oct 13 2021 We could also have a trailing with to allow Haskell style blah
- Timon Gehr (6/7) Oct 13 2021 I don't think this is an "improvement" of the `with` statement. It's a
- Andrei Alexandrescu (3/11) Oct 13 2021 You're right. The `while (type)` and `while (type):` at top level is
- Tejas (12/24) Oct 14 2021 Maybe having the syntax as:
- jfondren (62/73) Oct 14 2021 This is completely novel syntax (for d), when the proposed syntax
- Tejas (2/10) Oct 14 2021 Timon's tuple DIP should've been worked on further and accepted ;(
- Paul Backus (12/22) Oct 14 2021 In D this is accomplished using a regular block scope and a
- Tejas (18/40) Oct 14 2021 So is it safe to say what Andrei wants can be accomplished with
- Paul Backus (5/8) Oct 14 2021 Yes, but most syntax is syntax sugar. The main issue with it is
- Antonio (22/34) Oct 14 2021 - I usually take the functional way:
- Paul Backus (8/28) Oct 14 2021 I sometimes use [`std.functional.pipe`][1] for this:
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
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
On 2021-10-13 8:45, MrSmith wrote:On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei Alexandrescu wrote:Sadly no: https://run.dlang.io/is/Ja1uVk3. 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
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
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 [...]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 } ```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 } ?
Oct 13 2021
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
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:so proposal 1. is definitively not useful.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
On 2021-10-13 9:20, Paul Backus wrote:On Wednesday, 13 October 2021 at 13:16:36 UTC, Basile B. 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. This enables idioms such as "I want to create a temporary string and mess with it".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
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
On 2021-10-13 15:28, Adam D Ruppe wrote:On Wednesday, 13 October 2021 at 19:18:55 UTC, Andrei Alexandrescu wrote: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 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
On Wednesday, 13 October 2021 at 19:30:43 UTC, Andrei Alexandrescu wrote:On 2021-10-13 15:28, Adam D Ruppe wrote: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.On Wednesday, 13 October 2021 at 19:18:55 UTC, Andrei Alexandrescu wrote: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 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
On 10/13/21 10:30 PM, Mathias LANG wrote:On Wednesday, 13 October 2021 at 19:30:43 UTC, Andrei Alexandrescu wrote:Well extendedPathThen uses toStringz :o).On 2021-10-13 15:28, Adam D Ruppe wrote: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.On Wednesday, 13 October 2021 at 19:18:55 UTC, Andrei Alexandrescu wrote: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 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
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 hereThis 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
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
On 10/13/21 4:41 PM, Paul Backus wrote:On Wednesday, 13 October 2021 at 19:18:55 UTC, Andrei Alexandrescu wrote: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.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.
Oct 13 2021
On 2021-10-13 9:05, Adam D Ruppe wrote:On Wednesday, 13 October 2021 at 11:13:32 UTC, Andrei Alexandrescu wrote:Not generalizable.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 } --- ?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
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.html4. 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
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
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
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
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
On 10/13/21 4:13 PM, Timon Gehr wrote:On 10/13/21 1:13 PM, Andrei Alexandrescu wrote:You're right. The `while (type)` and `while (type):` at top level is probably good to salvage.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
On Thursday, 14 October 2021 at 02:58:32 UTC, Andrei Alexandrescu wrote:On 10/13/21 4:13 PM, Timon Gehr 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.On 10/13/21 1:13 PM, Andrei Alexandrescu wrote:You're right. The `while (type)` and `while (type):` at top level is probably good to salvage.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 14 2021
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=KAWA1DuvCnQIt could also be useful in `Python` style management for `file` handlesIt'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
On Thursday, 14 October 2021 at 08:37:40 UTC, jfondren wrote:On Thursday, 14 October 2021 at 07:12:29 UTC, Tejas wrote:Timon's tuple DIP should've been worked on further and accepted ;([...]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 [...]
Oct 14 2021
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
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: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?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
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
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
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 alternativeI 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