Structs and enums in Verse

Hey Verse team, I’d like to talk about structs and enums in Verse.

First things that came to my mind after being driven by Swift development almost daily for the 4-5 years are the following things:

  1. Why does Verse have no methods on structs and enums?
    Verse allows one to define extensions on any type, which includes structs and enums, but there’s no way to declare methods directly within the type body.
foo := struct {}
(Input: foo).MyPseudoStructMethod(): void = {}

# we could just permit the following syntax
foo := struct {
  MyPseudoStructMethod(): void = {}
}

Same applies to enums.

  1. Do you plan to introduce enums with associated values?

Right now enums are plain and only suitable for simple enumerations which does not allow their cases to carry any extra information. This makes it hard to implement simple but very highly useful and known types such as Either<A, B> as an enum.

Enums with associated values would be very beneficial if they also be permitted as @editable as long as all cases and the associated values are also @editable. This would allow the definition of much more ergonomic either like data structured within the UE editor.

We’d like to do more for both of these items, but don’t have concrete plans yet.

1 Like

EDIT: Note that I am biased, this is a genuine question - my background is mostly in C++ and Java, where in the former structs with methods is frowned upon by most, and the latter doesn’t even have structs - so forgive me if I’ve missed something :slight_smile:

Agree about enums, it would be nice to give them static/non-dynamic function capability just like Java and many other languages (though it’s really not essential, just might mean cleaner code), but structs…? Why should a struct have functions and when would it even be used? Also, how exactly is a struct with functions any different from a plain class with fields + functions?

The point of a struct is literally just to group variables together, so they can be used as a DTO or a Value Object; the goal of which is a more lightweight alternative to a full class for data encapsulation - specifically, separation logic.

Struct is for holding data, putting implementation into a strut means, by definition, it isn’t even a struct anymore - it’s a class - isn’t it? So what would be the purpose?

Hi there, it’s not a problem having a different opinion on it, that’s why we can discuss it here. I’ll try to write the things a bit simple for anyone else reading along.

As already shown above you can already associate methods to structs and enums in Verse via single extension syntax.

Both data types do not imply the strict requirement for the absence of any methods, that’s just just something past programming languages used to do. In several modern languages like Swift and Rust you can have ‘type members’ other than stored properties or associated values.

A class is ‘reference type’ which for example in Swift as far as I know is heap allocated. An instance of a class is an object and it follows ‘reference semantics’.

struct’s and enums are value types, but their instances can have both either ‘value’ or ‘reference semantics’ depending on the concrete implementation. For the most part those are stack allocated in Swift.

If we would attach any functions to any of these data types, those functions will first become members of the associated data types and we will refer to those as methods.

Methods are just functions, but their first parameter is always Self (in Verse context). The function itself is a reference type as it can be captured and referenced from somewhere else. A captured method should also keep the data instance alive, at least if such data instance is an object or otherwise we’re just talking about a copy of some pure data.

So in the end instances of classes, structs and enums (with associated values) are all holding data. Only the way that data is mutated, passed and referenced is different.

Having methods as type members on structs and enums enables some convenient syntactic usage, but furthermore it opens the door for protocol oriented programming. In Verse we’d rather call it ‘interface oriented programming’.

Right now an interface in Verse is limited to a ‘class’ only, but that’s just a temporary limitation as long as other data types don’t support more type members.

In fact in most cases you actually want to create pure data structured that hold data, but you also want to perform some actions on that data to manipulate it. That does not imply that you have to use classes and objects for those tasks.

In Swift for example, the majority to the types are structs enums and protocols (aka interfaces). Classes and actors are only used where you truly need reference semantics and objects.

Verse could for example expose all stdlib types such as int, logic, array, map etc. as structs in the future. Those could have explicit type members instead of single type associated extensions.

Here’s an example what Verse already is doing due to the current limitation:

# Module import path: /Verse.org/Verse
Verse<public> := module:
  (Input:[]t where t:type).Slice<public>(StartIndex:int, StopIndex:int)<computes><decides>: []t = external {}
  (Input:[]t where t:type).Slice<public>(StartIndex:int)<computes><decides>: []t = external {}
  (Input:[]t where t:type).Insert<public>(InsertionIndex:int, ElementsToInsert:[]t)<computes><decides>: []t = external {}

I can image we could collapse this to something more elegant and far easier to parse:

array(t: type) := struct {
  Slice<public>(StartIndex: int, StopIndex: int)<computes><decides>: array(t)
  Slice<public>(StartIndex: int)<computes><decides>: array(t)
  Insert<public>(InsertionIndex: int, ElementsToInsert: array(t))<computes><decides>: array(t)
  ...
}

As previously mentioned, having the ability to use interfaces on data structured other than classes would enable a lot of flexibility and generic algorithms.

All in all from my personal experience there’s a ton of advantages following that direction in the evolution of Verse.

2 Likes

I am really sorry for wasting your time, haha, I completely misunderstood your suggestion!

Yes, agree 100% - the way we can “extend objects with methods” right now is very nice, but the more traditional/standard style you suggest can definitely help with cleaner code.

Also I realized the next day that if enums can do X then sure, it makes sense to let structs do the same - they’re both essentially “lightweight data-holder classes” and being able to define utility methods within those actual classes is definitely useful.

So, TL;DR: my bad, I understand your suggestion properly now, and I completely agree :slight_smile:

As previously mentioned, having the ability to use interfaces on data structured other than classes would enable a lot of flexibility and generic algorithms.

Oh DEFINITELY. Interfaces are a great way to enforce contracts, and sometimes we want to enforce a contract on a “data holder” such as a struct, indeed.

1 Like