www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Return values from auto function

reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
I have auto function 'f' that might return either an error (with 
some text) or a result (with some value). The problem is that the 
type of the error is not the same as the type of result so 
compilation fails.

Here is my code:
----------
struct Result(T)
{
     struct Success
     {
         static if(!is(T == void))
         {
             T value;
         }
     }
     struct Failure
     {
         string error;
     }

     Algebraic!(Success, Failure) result;

     this(Success value)
     {
         result = value;
     }
     this(Failure value)
     {
         result = value;
     }
}
auto success(T)(T value)
{
     return Result!T(Result!T.Success(value));
}
auto failure(string error)
{
     return Result!void(Result!void.Failure(error));
}

auto f(int i)
{
     return i > 0 ? success(i) : failure("err text"); // Error: 
incompatible types for (success(i)) : (failure("err text")): 
Result!int and Result!void
}
-----------------

I can make it work if I add a type to `failure` function but this 
remove brevity in usage:
-----------------
auto failure(T)(string error)
{
     return Result!T(Result!T.Failure(error));
}
auto f(int i)
{
     return i > 0 ? success(i) : failure!int("err text");     // 
no error
}
-----------------

How can I make the original code compilable without templatizing 
`failure` function?
Nov 06 2020
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 6 November 2020 at 10:51:20 UTC, Andrey Zherikov wrote:
 I can make it work if I add a type to `failure` function but 
 this remove brevity in usage:
 -----------------
 auto failure(T)(string error)
 {
     return Result!T(Result!T.Failure(error));
 }
 auto f(int i)
 {
     return i > 0 ? success(i) : failure!int("err text");     // 
 no error
 }
 -----------------

 How can I make the original code compilable without 
 templatizing `failure` function?
You can't. Both return values have to have the same type, which means the failure function has to be able to return more than one type, which means it has to be a template.
Nov 06 2020
next sibling parent reply gbram <bpz3aurhcy cbd5o.anonbox.net> writes:
On Friday, 6 November 2020 at 12:03:01 UTC, Paul Backus wrote:
 On Friday, 6 November 2020 at 10:51:20 UTC, Andrey Zherikov 
 wrote:
 How can I make the original code compilable without 
 templatizing `failure` function?
You can't. Both return values have to have the same type, which means the failure function has to be able to return more than one type, which means it has to be a template.
Being pedantic, he can, by templatising 'f' instead.
Nov 06 2020
parent Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Friday, 6 November 2020 at 13:59:58 UTC, gbram wrote:
 On Friday, 6 November 2020 at 12:03:01 UTC, Paul Backus wrote:
 On Friday, 6 November 2020 at 10:51:20 UTC, Andrey Zherikov 
 wrote:
 How can I make the original code compilable without 
 templatizing `failure` function?
You can't. Both return values have to have the same type, which means the failure function has to be able to return more than one type, which means it has to be a template.
Being pedantic, he can, by templatising 'f' instead.
Could you share how?
Nov 06 2020
prev sibling parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Friday, 6 November 2020 at 12:03:01 UTC, Paul Backus wrote:
 You can't. Both return values have to have the same type, which 
 means the failure function has to be able to return more than 
 one type, which means it has to be a template.
This issue seems hit the inability to implicitly convert custom types. May be it makes more sense to ask in a separate thread.
Nov 06 2020
parent reply Jesse Phillips <Jesse.K.Phillips+D gmail.com> writes:
On Friday, 6 November 2020 at 14:20:40 UTC, Andrey Zherikov wrote:
 On Friday, 6 November 2020 at 12:03:01 UTC, Paul Backus wrote:
 You can't. Both return values have to have the same type, 
 which means the failure function has to be able to return more 
 than one type, which means it has to be a template.
This issue seems hit the inability to implicitly convert custom types. May be it makes more sense to ask in a separate thread.
The return type must be the same for all execution paths. Result!void is a different type from Result!int. You aren't passing a 'Result' because that doesn't exist as a type. Hopefully one of these captures a misunderstanding.
Nov 06 2020
parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Friday, 6 November 2020 at 14:58:40 UTC, Jesse Phillips wrote:
 On Friday, 6 November 2020 at 14:20:40 UTC, Andrey Zherikov 
 wrote:
 This issue seems hit the inability to implicitly convert 
 custom types. May be it makes more sense to ask in a separate 
 thread.
The return type must be the same for all execution paths. Result!void is a different type from Result!int. You aren't passing a 'Result' because that doesn't exist as a type.
To clarify my statement: Yes, Result!void and Result!int are different types but I couldn't find a way to implicitly convert one to another.
Nov 06 2020
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Friday, 6 November 2020 at 15:06:18 UTC, Andrey Zherikov wrote:

 To clarify my statement:
 Yes, Result!void and Result!int are different types but I 
 couldn't find a way to implicitly convert one to another.
You can't. Structs do not implicitly convert to each other, templated or otherwise.
Nov 06 2020
prev sibling parent reply Jesse Phillips <Jesse.K.Phillips+D gmail.com> writes:
On Friday, 6 November 2020 at 15:06:18 UTC, Andrey Zherikov wrote:
 On Friday, 6 November 2020 at 14:58:40 UTC, Jesse Phillips 
 wrote:
 On Friday, 6 November 2020 at 14:20:40 UTC, Andrey Zherikov 
 wrote:
 This issue seems hit the inability to implicitly convert 
 custom types. May be it makes more sense to ask in a separate 
 thread.
The return type must be the same for all execution paths. Result!void is a different type from Result!int. You aren't passing a 'Result' because that doesn't exist as a type.
To clarify my statement: Yes, Result!void and Result!int are different types but I couldn't find a way to implicitly convert one to another.
Putting aside D not providing implicit conversion to custom types. I'm curious what your semantics would be. How does the type know it can convert to int, or string, or Foo? What does it mean for a void to be an int? If you have something, what does it mean to go to not having that something. Would you really want to implicitly lose that something?
Nov 06 2020
parent Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Saturday, 7 November 2020 at 01:50:15 UTC, Jesse Phillips 
wrote:
 On Friday, 6 November 2020 at 15:06:18 UTC, Andrey Zherikov 
 wrote:
 On Friday, 6 November 2020 at 14:58:40 UTC, Jesse Phillips 
 wrote:
 On Friday, 6 November 2020 at 14:20:40 UTC, Andrey Zherikov 
 wrote:
 This issue seems hit the inability to implicitly convert 
 custom types. May be it makes more sense to ask in a 
 separate thread.
The return type must be the same for all execution paths. Result!void is a different type from Result!int. You aren't passing a 'Result' because that doesn't exist as a type.
To clarify my statement: Yes, Result!void and Result!int are different types but I couldn't find a way to implicitly convert one to another.
I'm curious what your semantics would be. How does the type know it can convert to int, or string, or Foo? What does it mean for a void to be an int? If you have something, what does it mean to go to not having that something. Would you really want to implicitly lose that something?
Ideally error type shouldn't depend on what type the operation returns but language has a limitation that function must return single type. Technically Failure struct can be moved out from Result(T) but the issue remains the same: how to convert Failure to Result!T for any T.
Nov 07 2020
prev sibling next sibling parent reply Ferhat =?UTF-8?B?S3VydHVsbXXFnw==?= <aferust gmail.com> writes:
On Friday, 6 November 2020 at 10:51:20 UTC, Andrey Zherikov wrote:
 I have auto function 'f' that might return either an error 
 (with some text) or a result (with some value). The problem is 
 that the type of the error is not the same as the type of 
 result so compilation fails.

 [...]
Sounds like Andrei's "Expected". Here is an implementation for d. https://github.com/tchaloupka/expected
Nov 06 2020
parent Jesse Phillips <Jesse.K.Phillips+D gmail.com> writes:
On Friday, 6 November 2020 at 20:05:36 UTC, Ferhat Kurtulmuş 
wrote:
 On Friday, 6 November 2020 at 10:51:20 UTC, Andrey Zherikov 
 wrote:
 I have auto function 'f' that might return either an error 
 (with some text) or a result (with some value). The problem is 
 that the type of the error is not the same as the type of 
 result so compilation fails.

 [...]
Sounds like Andrei's "Expected". Here is an implementation for d. https://github.com/tchaloupka/expected
Hmmm, I wonder how that is different from the idea of 'option'
Nov 06 2020
prev sibling next sibling parent reply James Blachly <james.blachly gmail.com> writes:
On 11/6/20 5:51 AM, Andrey Zherikov wrote:
 I have auto function 'f' that might return either an error (with some 
 text) or a result (with some value). The problem is that the type of the 
 error is not the same as the type of result so compilation fails.
 
...
 How can I make the original code compilable without templatizing 
 `failure` function?
Paul Backus has already replied to you so I am surprised he did not plug is own package "SumType". Maybe it is modesty? This seems like an ideal time to use this. All of that being said, I tried to solve this for you using Paul's SumType package and myself was initially stymied by the fact that the implicit conversion of a `Success(T)` or `Failure` type did not work within the ternary op expression*. I rewrote it with if/else which works great and is only slightly more verbose than with ternary op. ```D alias Result = SumType!(Success!int, Failure); auto f(int i) { Result retval; if (i > 0) retval = Success!int(i); else retval = Failure("Sorry!"); return retval; } ``` the above relies on suitable definition of `Success(T)` and `Failure` structs, obviously. * This fails due to different types within the same expression: ``` retval = i > 0 ? Success!int(i) : Failure("Sorry"); ``` casting each to `Result` compiles, but is verbose: ``` return i > 0 ? cast(Result) Success!int(i) : cast(Result) Failure("Sorry"); ``` ** Could someone more knowledgeable than me explain why implicit conversion does not happen with the ternary op, but works fine with if/else? Presumably, it is because the op returns a single type and implicit conversion is performed after computing the expression's return type? If this somehow worked, it would make the SumType package much more ergonomic **
Nov 07 2020
next sibling parent Patrick Schluter <Patrick.Schluter bbox.fr> writes:
On Saturday, 7 November 2020 at 15:49:13 UTC, James Blachly wrote:
 ```
 retval = i > 0 ? Success!int(i) : Failure("Sorry");
 ```

 casting each to `Result` compiles, but is verbose:

 ```
     return i > 0 ? cast(Result) Success!int(i) : cast(Result) 
 Failure("Sorry");
 ```

 ** Could someone more knowledgeable than me explain why 
 implicit conversion does not happen with the ternary op, but 
 works fine with if/else? Presumably, it is because the op 
 returns a single type and implicit conversion is performed 
 after computing the expression's return type? If this somehow 
 worked, it would make the SumType package much more ergonomic **
It's just that tenary requires the same type in both branches. It was already so in C. return i > 0 ? (retval = Success!int(i)) : (retval = Failure("Sorry")); should work
Nov 07 2020
prev sibling parent Jesse Phillips <Jesse.K.Phillips+D gmail.com> writes:
On Saturday, 7 November 2020 at 15:49:13 UTC, James Blachly wrote:
 ```
     return i > 0 ? cast(Result) Success!int(i) : cast(Result) 
 Failure("Sorry");
 ```
I don't know about the SumType but I would expect you could use a construction instead of cast. import std; alias Result = Algebraic!(int, string) ; void main() {    auto x = true? Result("fish") : Result(6); }
Nov 07 2020
prev sibling parent Piotr Mitana <the.mail.of.mi2 gmail.com> writes:
On Friday, 6 November 2020 at 10:51:20 UTC, Andrey Zherikov wrote:

 struct Result(T)
 {
     struct Success
     {
         static if(!is(T == void))
         {
             T value;
         }
     }
     struct Failure
     {
         string error;
     }

     Algebraic!(Success, Failure) result;

     this(Success value)
     {
         result = value;
     }
     this(Failure value)
     {
         result = value;
     }
 }
 auto success(T)(T value)
 {
     return Result!T(Result!T.Success(value));
 }
 auto failure(string error)
 {
     return Result!void(Result!void.Failure(error));
 }

 auto f(int i)
 {
     return i > 0 ? success(i) : failure("err text"); // Error: 
 incompatible types for (success(i)) : (failure("err text")): 
 Result!int and Result!void
 }
I'd go with: struct Result(T) { Nullable!string error; static if(!is(T == void)) { T value; static Result!T success(T value) { return Result!T(Nullable!string.init, value); } static Result!T failure(string error) { return Result!T(nullable(error), T.init); } } else { static Result!T success() { return Result!T(Nullable!string.init); } static Result!T failure(string error) { return Result!T(nullable(error)); } } bool isSuccess() { return error.isNull(); } } // Demo Result!int intFun(int i) { return i > 0 ? Result!int.success(i) : Result!int.failure("Be positive!"); } Result!void voidFun(int i) { return i > 0 ? Result!void.success() : Result!void.failure("Be positive!"); } void main() { auto intFunRes = intFun(5); if(intFunRes.isSuccess) writefln("Result: %d", intFunRes.value); else writefln("ERROR: %s", intFunRes.error.get); } Does this satisfy your needs?
Nov 07 2020