www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - SumType extraction

reply Josh Holtrop <jholtrop gmail.com> writes:
Hello all. In my application I came across a desire to store an 
ordered array of handles that could point to one of several 
different objects, and it seems like the tool I want for that is 
SumType.

I started with something like (simplified of course):

```d
class Foo {}
class Bar {}

alias Item = SumType!(Foo, Bar);
```

And then I could do:

```d
Item[] items;
items ~= Item(new Foo());
```

But, I found I wanted while iterating through my items to 
sometimes only operate on those of a certain type. Rather than 
having to call SumType.match! and specify patterns to test if 
they had the type I wanted, I wanted a more concise syntax, and 
also the ability to just directly extract the handle, or null if 
the item kind wasn't what I was asking for.

So I came up with:

```d
struct Item
{
     SumType!(Foo, Bar) item;
     alias item this;

     this(T)(T v)
     {
         item = v;
     }

     bool is_a(T)()
     {
         return item.match!(
                 (T v) => true,
                 _ => false);
     }

     T get(T)()
     {
         return item.match!(
                 (T v) => v,
                 _ => null);
     }
}
```

This seems to give me the syntax that I want, so I can do things 
like:

```d
foreach (item; items)
{
     if (Foo foo = item.get!Foo)
     {
         /* do something with foo */
     }
}
```

I realized that I could stick with defining `Item` as an `alias` 
and use UFCS to define global `is_a` or `get`, but I prefer 
having `is_a` and `get` scoped to the `Item` struct instead of 
globally defined.

Questions:

1. Would there be something more appropriate than SumType for 
what I'm trying to do?
2. Am I missing anything with a short syntax like my is_a() or 
get() that already exists in SumType so I wouldn't need to define 
my own struct to wrap it?
3. If not, could something like these two be added to SumType for 
more direct access?
4. Any other general improvements to my solution?
Jun 27
next sibling parent reply drug007 <drug2004 bk.ru> writes:
What prevents you from doing:
```D
import std.sumtype;

class Foo {}
class Bar {}

alias Item = SumType!(Foo, Bar);

void main()
{
     Item[] items = [Item(new Foo()), Item(new Bar()), Item(new Foo()), 
Item(new Bar())];
     foreach (item; items)
     {
         item.match!(
             (Foo v) { /* do something with foo */ },
             (_) {}
         );
     }
}
```
?
It's more effective by the way - you check the type once only.
Jun 28
parent reply Josh Holtrop <jholtrop gmail.com> writes:
On Friday, 28 June 2024 at 10:52:01 UTC, drug007 wrote:
 What prevents you from doing:
 ```D
 import std.sumtype;

 class Foo {}
 class Bar {}

 alias Item = SumType!(Foo, Bar);

 void main()
 {
     Item[] items = [Item(new Foo()), Item(new Bar()), Item(new 
 Foo()), Item(new Bar())];
     foreach (item; items)
     {
         item.match!(
             (Foo v) { /* do something with foo */ },
             (_) {}
         );
     }
 }
 ```
 ?
 It's more effective by the way - you check the type once only.
Nothing prevents that, and indeed I still plan to use item.match! like that when I need to handle multiple/all types. I just wanted the get! functionality when I only expect or want to handle one type without all the additional pattern matching syntax. But, I think my: ```d if (Foo foo = item.get!Foo) { /* do something with foo */ } ``` is still only checking the type once due to the one call to match! in get!, right?
Jun 28
parent reply drug007 <drug2004 bk.ru> writes:
On 28.06.2024 15:43, Josh Holtrop wrote:
 On Friday, 28 June 2024 at 10:52:01 UTC, drug007 wrote:
 
 Nothing prevents that, and indeed I still plan to use item.match! like 
 that when I need to handle multiple/all types. I just wanted the get! 
 functionality when I only expect or want to handle one type without all 
 the additional pattern matching syntax.
 
 But, I think my:
 
 ```d
      if (Foo foo = item.get!Foo)
      {
          /* do something with foo */
      }
 ```
 
 is still only checking the type once due to the one call to match! in 
 get!, right?
Both yes and no, you check the type once, but then check for null, so a double check is performed nonetheless. But for me it's a minor difference. There are two common ways to handle sumtypes: using either an explicit type tag or implicit type handling. Both have their pros and cons. As I know (can be wrong) std.sumtype implies type handlers not type tags.
Jun 28
parent Josh Holtrop <jholtrop gmail.com> writes:
On Friday, 28 June 2024 at 22:25:40 UTC, drug007 wrote:
 Both yes and no, you check the type once, but then check for 
 null, so a double check is performed nonetheless. But for me 
 it's a minor difference.

 There are two common ways to handle sumtypes: using either an 
 explicit type tag or implicit type handling. Both have their 
 pros and cons. As I know (can be wrong) std.sumtype implies 
 type handlers not type tags.
Ah, I see what you're saying. I suppose if performance was more important I would do this a different way. I'm mainly going for the more concise syntax for this application. Thanks!
Jun 28
prev sibling next sibling parent Christian =?UTF-8?B?S8O2c3RsaW4=?= <christian.koestlin gmail.com> writes:
On Thursday, 27 June 2024 at 18:51:19 UTC, Josh Holtrop wrote:
 Questions:
 4. Any other general improvements to my solution?
I know it's kind of an unpopular choice these days but one could go with inheritance and polymorphism or instanceof tests. something along the lines of ```d import std.stdio : writeln; class Item { public void operationA() { } public void operationB() { } } class ItemA : Item { override public void operationA() { writeln("ItemA"); } } class ItemB : Item { override public void operationB() { writeln("ItemB"); } } void main(string[] args) { auto items = [new ItemA(), new ItemB()]; writeln("operation a:"); foreach (item; items) { item.operationA(); } writeln("operation b:"); foreach (item; items) { item.operationB(); } writeln("instance of:"); foreach (item; items) { if (auto itemB = cast(ItemB) item) { writeln("Found an ItemB"); } } } ``` drawback might be, that if you add a new subtype the compiler will not warn you that you did not implement one case for one of the implementations. Kind regards, Christian
Jul 06
prev sibling next sibling parent Lance Bachmeier <no spam.net> writes:
On Thursday, 27 June 2024 at 18:51:19 UTC, Josh Holtrop wrote:
 Hello all. In my application I came across a desire to store an 
 ordered array of handles that could point to one of several 
 different objects, and it seems like the tool I want for that 
 is SumType.

 I started with something like (simplified of course):

 ```d
 class Foo {}
 class Bar {}

 alias Item = SumType!(Foo, Bar);
 ```

 And then I could do:

 ```d
 Item[] items;
 items ~= Item(new Foo());
 ```

 But, I found I wanted while iterating through my items to 
 sometimes only operate on those of a certain type. Rather than 
 having to call SumType.match! and specify patterns to test if 
 they had the type I wanted, I wanted a more concise syntax, and 
 also the ability to just directly extract the handle, or null 
 if the item kind wasn't what I was asking for.
Have you considered [std.Variant](https://dlang.org/phobos/std_variant.html)? I've found that to be the more convenient choice if I know the type of an item. Something like this (untested code): ``` import std.variant; Variant[] items; items ~= Variant(new Foo()); // If I know items[0] is a Foo Foo foo = *(items[0].peek!Foo); // If I want to check that it's actually Foo auto foo = items[0].peek!Foo; if (foo !is null) { // Do something with *foo } ```
Jul 06
prev sibling parent An Pham <home home.com> writes:
On Thursday, 27 June 2024 at 18:51:19 UTC, Josh Holtrop wrote:
 Hello all. In my application I came across a desire to store an 
 ordered array of handles that could point to one of several 
 different objects, and it seems like the tool I want for that 
 is SumType.

 I started with something like (simplified of course):

 ```d
 class Foo {}
 class Bar {}
My Variant package can do this type of thing https://github.com/apz28/dlang/blob/main/source/pham/var/var_variant.d#L3430 Variant[] mixedC; mixedC ~= new Foo(); mixedC ~= new Bar(); size_t foundCount; foreach (v; mixedC) { if (auto c = v.peek!Foo) { foundCount++; } } assert(foundCount == 1);
Jul 06