November 07, 2020
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.

# Result!void => Result!int

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?

# Result!int => Result!void

If you have something, what does it mean to go to not having that something. Would you really want to implicitly lose that something?

November 07, 2020
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.
>
> # Result!void => Result!int
>
> 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?
>
> # Result!int => Result!void
>
> 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.
November 07, 2020
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 **
November 07, 2020
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
November 07, 2020
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);

}
November 07, 2020
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?
1 2
Next ›   Last »