June 16, 2020
On Tuesday, 16 June 2020 at 12:21:56 UTC, Paul Backus wrote:
> On Monday, 15 June 2020 at 21:48:19 UTC, Dukc wrote:
>>> Is there a way I can improve SumType's documentation to make its behavior in this regard clearer?
>>
>> It's not documentations fault. But if you want to supersede Taggedalgebraic really hard, you can try convincing Mike to let you write a project highlight in the D blog, to raise awareness for thickskulls like me :D.
>
> That's an interesting idea. I'll look into it after the upcoming 1.0.0 release.
>
>>> In Rust's `enum` types you cannot access the tag directly.
>>
>> Again bad research from my part. But it's something I want to do. Why? `final switch` statements. I may want to `return` or `break` directly from the switch, and in that case I can't use pattern matching without extra thinking. Well, on D anyway. I don't know about Rust.
>
> If it's not too much trouble, can you give an example of the kind of thing you'd like to be able to do? I suspect it's possible with SumType, but it may not be obvious how, which makes it a good candidate for the documentation's "examples" section.

I'm not Dukc, but here's an example I came up with:

float myFun() {
    MyTaggedUnion!(float, int, string, int[]) a = fun();

    float value;
    final switch (a.type) {
        case a._float:
            return a.as!float();
        case a._int:
            return a.as!int();
        case a._string:
            value = a.as!string().process();
        case a._intArray:
            value = a.as!(int[]).process();
    }

    // Lots of code that manipulates 'value'.
    if (a.type == a._string) {
       // Do something special for only one of the types,
       // but after doing lots of common things
    }
    // More code that manipulates 'value'.

    return value;
}

Essentially, there's shortcut returns for a few cases, but the others have some common behavior, even better if this is slightly different in one single place for one single held type, so you can't really factor it out as a separate function.

--
  Simen
June 16, 2020
On Tuesday, 16 June 2020 at 12:21:56 UTC, Paul Backus wrote:
> If it's not too much trouble, can you give an example of the kind of thing you'd like to be able to do?

No problem. This is directly from real code. The idea is that I have a vector shape `vecImage.front` and I want to figure out whether I need to reverse it, so that it will run in wanted direction `newDir`. However, the `determineCycleDirection` function is not perfect, so this resulted:

```
const currentDir = vecImage.front.save.determineCycleDirection;
final switch(currentDir.kind)
{	case DetectedCycleDir.Kind.indifferent:
	{	turnNeeded = 0;
		break;
	}
			
	case DetectedCycleDir.Kind.selfCrossed:
	{	auto errRes = Message.directionSearchSelfcross(fileName).only.array;
		return WaypointsOrError.errors(errRes);
	}
			
	case DetectedCycleDir.Kind.timeout:
	{	auto errRes = Message.directionSearchTimeout(fileName).only.array;
		return WaypointsOrError.errors(errRes);
	}
			
	case DetectedCycleDir.Kind.known:
	{	turnNeeded = currentDir.knownValue == newDir? 0: 1;
		break;
	}
}
```

DetectedCycleDir definition (comments translated to English from the project):

```
union _DetectedCycleDir
{	import taggedalgebraic.taggedunion : Void;
	Void timeout; //exection broken, as it seemed to end up in infinite loop
	Void selfCrossed; //The shape outline crossed itself
	Void indifferent; //The direction is meaningless (0 - 2 points)
	CycleDirection known;
}
alias DetectedCycleDir = from!"taggedalgebraic".TaggedUnion!_DetectedCycleDir;
```
June 16, 2020
On Tuesday, 16 June 2020 at 12:58:50 UTC, Dukc wrote:
> so this resulted: [snip]

I might add that I should have used the `with` statement to cut on verbosity:

```
with (DetectedCycleDir.Kind) final switch(currentDir.kind)
{	case indifferent: ...
 	case known: ...
}
```

June 16, 2020
On Tuesday, 16 June 2020 at 12:58:50 UTC, Dukc wrote:
> On Tuesday, 16 June 2020 at 12:21:56 UTC, Paul Backus wrote:
>> If it's not too much trouble, can you give an example of the kind of thing you'd like to be able to do?
>
> No problem. This is directly from real code. The idea is that I have a vector shape `vecImage.front` and I want to figure out whether I need to reverse it, so that it will run in wanted direction `newDir`. However, the `determineCycleDirection` function is not perfect, so this resulted:

The most straightforward way to do this with SumType is to use a variable to track whether an early return is needed:

    const currentDir = vecImage.front.save.determineCycleDirection;
    auto errRes; // replace with appropriate type

    bool err = currentDir.match!(
        (Indifferent _) {
            turnNeeded = 0;
            return false;
        },
        (SelfCrossed _) {
            errRes = Message.directionSearchSelfCross(fileName).only.array;
            return true;
        },
        (Timeout _) {
            errRes = Message.directionSearchTimeout(fileName).only.array;
            return true;
        },
        (CycleDirection known) {
            turnNeeded = currentDir.knownValue == newDir ? 0 : 1;
            return false;
        }
    );

    if (err) return WaypointsOrError.errors(errRes);

Where DetectedCycleDir is defined as something like this:

    struct Timeout {}
    struct SelfCrossed {}
    struct Indifferent {}

    alias DetectedCycleDir = SumType!(
        Timeout,
        SelfCrossed,
        Indifferent,
        CycleDirection
    );

Of course, this is only looking at a single fragment in isolation. With knowledge of the full function this is taken from, it would probably be possible to come up with a more elegant way of refactoring it--perhaps by using an `Optional` or `Result` type, or by splitting the parts before and after the early return into independent functions.
June 16, 2020
On Tuesday, 16 June 2020 at 12:44:54 UTC, Simen Kjærås wrote:
> On Tuesday, 16 June 2020 at 12:21:56 UTC, Paul Backus wrote:

>> If it's not too much trouble, can you give an example of the kind of thing you'd like to be able to do? I suspect it's possible with SumType, but it may not be obvious how, which makes it a good candidate for the documentation's "examples" section.
>
> I'm not Dukc, but here's an example I came up with:
>
> float myFun() {
>     MyTaggedUnion!(float, int, string, int[]) a = fun();
>
>     float value;
>     final switch (a.type) {
>         case a._float:
>             return a.as!float();
>         case a._int:
>             return a.as!int();
>         case a._string:
>             value = a.as!string().process();
>         case a._intArray:
>             value = a.as!(int[]).process();
>     }
>
>     // Lots of code that manipulates 'value'.
>     if (a.type == a._string) {
>        // Do something special for only one of the types,
>        // but after doing lots of common things
>     }
>     // More code that manipulates 'value'.
>
>     return value;
> }
>
> Essentially, there's shortcut returns for a few cases, but the others have some common behavior, even better if this is slightly different in one single place for one single held type, so you can't really factor it out as a separate function.

I wonder how that final switch would look like if the tagged union contains a type such as Some!(Other!(immutable(int)[]), 3). I.e. how would you spell out the case :)

June 16, 2020
On Tuesday, 16 June 2020 at 12:44:54 UTC, Simen Kjærås wrote:
>
> I'm not Dukc, but here's an example I came up with:
>
[...]
>
> Essentially, there's shortcut returns for a few cases, but the others have some common behavior, even better if this is slightly different in one single place for one single held type, so you can't really factor it out as a separate function.
>
> --
>   Simen

I think this is an instructive example, because it illustrates one of the key
differences between functional-style code and imperative-style code: explicit
coupling via funcion parameters and return values vs. implicit coupling via
shared state.

Here's how I would write it:

    float common1(float value)
    {
        // Lots of code that manipulates 'value'
    }

    float common2(float value)
    {
        // More code that manupulates 'value'
    }

    float special(string s, float value)
    {
        // Do something special for only one of the types,
        // but after doing lots of common things
    }

    float myFun(SumType!(float, int, string, int[]) a)
    {
        import std.functional: pipe;

        return a.match!(
            number => cast(float) number,
            (string s) =>
                s.process
                    .common1
                    .pipe!(value => special(s, value))
                    .common2
            (int[] ia) =>
                ia.process
                    .common1
                    .common2
        );
    }

You'll notice that I've taken the liberty of extracting each arbitrary "chunk"
of code indicated by a comment into its own function. In doing so, I've had to
make the inputs and outputs of those chunks explicit (and make some assumptions
about what they are). While this is a bit of extra work up front, I think it
makes the end result easier to understand--not to mention easier to unit test.

Exercise for the reader: how can this code be further refactored to eliminate
the duplication between the string and int[] cases?

(Solution: https://gist.github.com/pbackus/deda874eeddf587d938cb5d6213a0b84)
June 16, 2020
On Tuesday, 16 June 2020 at 16:26:14 UTC, Paul Backus wrote:
> Here's how I would write it:
>
>     float common1(float value)
>     {
>         // Lots of code that manipulates 'value'
>     }
>
>     float common2(float value)
>     {
>         // More code that manupulates 'value'
>     }
>
>     float special(string s, float value)
>     {
>         // Do something special for only one of the types,
>         // but after doing lots of common things
>     }
>
>     float myFun(SumType!(float, int, string, int[]) a)
>     {
>         import std.functional: pipe;
>
>         return a.match!(
>             number => cast(float) number,
>             (string s) =>
>                 s.process
>                     .common1
>                     .pipe!(value => special(s, value))
>                     .common2
>             (int[] ia) =>
>                 ia.process
>                     .common1
>                     .common2
>         );
>     }

Despite my need to use the tag value directly, I do agree that this style should be preferable.

I am not generally very fond of littering code with extra variables, that converting `switch`es directly to `match`s or `visit`s requires. But keeping the general code architecture as good as in your example, should mostly avoid that need. And even if I do need extra variables, it should be no problem when such logic is encapsulated well enough.

Something to consider for me, regardless of which of the two DUB packages I end up using in the long term.
June 16, 2020
On Tuesday, 16 June 2020 at 19:15:24 UTC, Dukc wrote:
> I am not generally very fond of littering code with extra variables, that converting `switch`es directly to `match`s or `visit`s requires. But keeping the general code architecture as good as in your example, should mostly avoid that need.

Meant need for extra variables, not need for converting the `switch`es.


1 2 3
Next ›   Last »