www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - scope(exit) with expected library

reply WebFreak001 <d.forum webfreak.org> writes:
Would it be possible to extend `scope(exit)` and `scope(success)` 
to trigger properly for functions returning `Expected!T` as 
defined in the 
[expectations](https://code.dlang.org/packages/expectations) and 
[expected](https://code.dlang.org/packages/expected) DUB 
libraries?

For example is it possible to make this work as expected:
```d
Expected!int divide(int a, int b) nothrow
{
     scope (failure) writeln("division failed");
     scope (success) writeln("division succeeded");

     if (b == 0) return err!int("division by zero");
     return ok(a / b);
}
```
Aug 25 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 25 August 2021 at 14:04:54 UTC, WebFreak001 wrote:
 Would it be possible to extend `scope(exit)` and 
 `scope(success)` to trigger properly for functions returning 
 `Expected!T` as defined in the 
 [expectations](https://code.dlang.org/packages/expectations) 
 and [expected](https://code.dlang.org/packages/expected) DUB 
 libraries?

 For example is it possible to make this work as expected:
 ```d
 Expected!int divide(int a, int b) nothrow
 {
     scope (failure) writeln("division failed");
     scope (success) writeln("division succeeded");

     if (b == 0) return err!int("division by zero");
     return ok(a / b);
 }
 ```
Probably the only principled way to make this work would be to define some kind of "concept"/structural interface that's recognized by the compiler to mean "this is an error-handling type", in the same way that the compiler recognizes `empty`/`front`/`popFront` to mean "this is an iterable type". Even then, it would require some pretty invasive language changes (and some pretty gnarly code in the compiler), but it's at least *theoretically* possible.
Aug 25 2021
next sibling parent WebFreak001 <d.forum webfreak.org> writes:
On Wednesday, 25 August 2021 at 14:22:26 UTC, Paul Backus wrote:
 On Wednesday, 25 August 2021 at 14:04:54 UTC, WebFreak001 wrote:
 [...]
Probably the only principled way to make this work would be to define some kind of "concept"/structural interface that's recognized by the compiler to mean "this is an error-handling type", in the same way that the compiler recognizes `empty`/`front`/`popFront` to mean "this is an iterable type". Even then, it would require some pretty invasive language changes (and some pretty gnarly code in the compiler), but it's at least *theoretically* possible.
do you think this would be worth a DIP that could get in? Or should usage in these packages grow first?
Aug 25 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 8/25/21 10:22 AM, Paul Backus wrote:
 On Wednesday, 25 August 2021 at 14:04:54 UTC, WebFreak001 wrote:
 Would it be possible to extend `scope(exit)` and `scope(success)` to 
 trigger properly for functions returning `Expected!T` as defined in 
 the [expectations](https://code.dlang.org/packages/expectations) and 
 [expected](https://code.dlang.org/packages/expected) DUB libraries?

 For example is it possible to make this work as expected:
 ```d
 Expected!int divide(int a, int b) nothrow
 {
     scope (failure) writeln("division failed");
     scope (success) writeln("division succeeded");

     if (b == 0) return err!int("division by zero");
     return ok(a / b);
 }
 ```
Probably the only principled way to make this work would be to define some kind of "concept"/structural interface that's recognized by the compiler to mean "this is an error-handling type", in the same way that the compiler recognizes `empty`/`front`/`popFront` to mean "this is an iterable type". Even then, it would require some pretty invasive language changes (and some pretty gnarly code in the compiler), but it's at least *theoretically* possible.
I think it's possible to work with some mechanics that aren't necessarily desirable. Something like: ```d ErrorHandler error = registerErrorHandler; error.onFailure({writeln("division failed");}); error.onSuccess({writeln("division succeeded");}); ... ``` On returning `err`, the registration would trigger a flag saying an error is occurring, and call the right callback when `ErrorHandler` is destructing. The cleanup of the return value would clean up the error condition. It would be messy and likely brittle. I've also advocated in the past that it would be nice to have access to the things that are causing the success, failure, etc. Like `scope(failure, exception) writeln("Exception being thrown is ", exception)` Could be extended to: ```d scope(success, r) if(r.isError) writeln("division failed"); else writeln("division succeeded"); ``` That `scope(success)` kinda sucks though... -Steve
Aug 25 2021
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 8/25/21 10:42 AM, Steven Schveighoffer wrote:

 
 I think it's possible to work with some mechanics that aren't 
 necessarily desirable. Something like:
 
One has to weigh how much this is preferred to actual exception handling... If something like DIP1008 could become usable, it might alleviate even the need for such things. -Steve
Aug 25 2021
parent WebFreak001 <d.forum webfreak.org> writes:
On Wednesday, 25 August 2021 at 14:52:34 UTC, Steven 
Schveighoffer wrote:
 On 8/25/21 10:42 AM, Steven Schveighoffer wrote:

 
 I think it's possible to work with some mechanics that aren't 
 necessarily desirable. Something like:
 
One has to weigh how much this is preferred to actual exception handling... If something like DIP1008 could become usable, it might alleviate even the need for such things. -Steve
DIP1008 is a nice step forward, but I think sometimes explicit error handling is nicer, and especially with C interop and concurrency (like vibe.d tasks) `nothrow` is pretty good to have.
Aug 25 2021
prev sibling next sibling parent reply WebFreak001 <d.forum webfreak.org> writes:
On Wednesday, 25 August 2021 at 14:42:07 UTC, Steven 
Schveighoffer wrote:
 On 8/25/21 10:22 AM, Paul Backus wrote:
 On Wednesday, 25 August 2021 at 14:04:54 UTC, WebFreak001 
 wrote:
 [...]
Probably the only principled way to make this work would be to define some kind of "concept"/structural interface that's recognized by the compiler to mean "this is an error-handling type", in the same way that the compiler recognizes `empty`/`front`/`popFront` to mean "this is an iterable type". Even then, it would require some pretty invasive language changes (and some pretty gnarly code in the compiler), but it's at least *theoretically* possible.
I think it's possible to work with some mechanics that aren't necessarily desirable. Something like: ```d ErrorHandler error = registerErrorHandler; error.onFailure({writeln("division failed");}); error.onSuccess({writeln("division succeeded");}); ... ``` On returning `err`, the registration would trigger a flag saying an error is occurring, and call the right callback when `ErrorHandler` is destructing. The cleanup of the return value would clean up the error condition. It would be messy and likely brittle.
Hm I'm not quite seeing how the error handler is related to an "Expected type interface" that the compiler could expect. Currently with exceptions the scope things are implemented using try-catch-finally, this would be even simpler: ```d scope(exit) exit(); scope(success) success(); scope(failure) failure(); return something(); ``` lowers to ```d auto ret = something(); if (ret.isError) failure(); if (!ret.isError) success(); exit(); return ret; ``` for all return statements. I might be missing some obvious drawbacks here but I think this sounds reasonable and comparable with the try-catch-finally lowering. As Paul Backus suggested the compiler could check if the return type has for example `is(typeof(return.isError) : bool)` and maybe also if the function is `nothrow`.
Aug 25 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 8/25/21 10:58 AM, WebFreak001 wrote:

 
 Hm I'm not quite seeing how the error handler is related to an "Expected 
 type interface" that the compiler could expect.
This would be without compiler changes.
 Currently with exceptions the scope things are implemented using 
 try-catch-finally, this would be even simpler:
 
 ```d
 scope(exit) exit();
 scope(success) success();
 scope(failure) failure();
 
 return something();
 ```
 
 lowers to
 
 ```d
 auto ret = something();
 if (ret.isError) failure();
 if (!ret.isError) success();
 exit();
 return ret;
 ```
 
 for all return statements.
 
 I might be missing some obvious drawbacks here but I think this sounds 
 reasonable and comparable with the try-catch-finally lowering.
It does sound pretty reasonable. But overloading these existing features might make things confusing.
 
 As Paul Backus suggested the compiler could check if the return type has 
 for example `is(typeof(return.isError) : bool)` and maybe also if the 
 function is `nothrow`.
Another approach is to let the compiler deal with the error handling and not muddy your return type. Swift does something similar, where it rewrites the throw/catch into a standard return and doesn't do actual thrown exceptions. There are some caveats, but if we could fit this kind of error handling into mostly-similar syntax (i.e. the same ease of exceptions without the actual problems that exceptions and stack unwinding bring), it might make things much easier to transition. -Steve
Aug 25 2021
parent WebFreak001 <d.forum webfreak.org> writes:
On Wednesday, 25 August 2021 at 15:30:57 UTC, Steven 
Schveighoffer wrote:
 [...]

 Another approach is to let the compiler deal with the error 
 handling and not muddy your return type. Swift does something 
 similar, where it rewrites the throw/catch into a standard 
 return and doesn't do actual thrown exceptions. There are some 
 caveats, but if we could fit this kind of error handling into 
 mostly-similar syntax (i.e. the same ease of exceptions without 
 the actual problems that exceptions and stack unwinding bring), 
 it might make things much easier to transition.

 -Steve
I like the Swift error handling, so I think this would be a great idea for nothrow code with error handling too.
Aug 25 2021
prev sibling parent Paul Backus <snarwin gmail.com> writes:
On Wednesday, 25 August 2021 at 14:42:07 UTC, Steven 
Schveighoffer wrote:
 I think it's possible to work with some mechanics that aren't 
 necessarily desirable. Something like:

 ```d
 ErrorHandler error = registerErrorHandler;
 error.onFailure({writeln("division failed");});
 error.onSuccess({writeln("division succeeded");});

 ...
 ```
This is kind of like what Common Lisp has with its condition system. One downside of this approach in D is that it would not work well with static analysis features like ` safe`, `nothrow`, etc., since the handlers are not known until runtime. But it does offer a lot more flexibility than traditional exceptions.
Aug 25 2021