VL-Language
VL-Language copied to clipboard
[Quest] Enums as first class citizens
At the time of this writing, one has to define Enums in c# and import them. But enums are nice and can enhance the understanding of patches a lot compared to bools or other constructs.
An additional question that came to my mid is if it would be nice to have the int values for enums defined manually or automatically, so that one can use bitwise operations on them later? those bitwise operations would then maybe need another nodeset/syntactic-sugar.
like so:
private enum Targets : uint
{
None = 0,
foo= 1,
bar= 2,
foobar= 4,
anotherfoo= 8,
anotherbar= 16,
anotherfoobar= 32,
All = uint.MaxValue
}
that said, i'm not sure about the (u)int stuff at all
Yeah, it's a shame to have to go to C# to express something that simple.
I am unsure about the idea to limit the number of possible entries to 64 + None
+ All
. On the other hand, it sounds like a lot...
Let us contrast this idea to how C# interprets an enum definition with
- unspecified integer values for enum entries
- unspecified backing type
It magically does this:
private enum Targets : int
{
foo= 0,
bar= 1,
foobar= 2,
anotherfoo= 3,
anotherbar= 4,
anotherfoobar= 5
}
https://docs.microsoft.com/de-de/dotnet/csharp/language-reference/builtin-types/enum
I am totally fine with doing stuff differently if we have a good reason. Maybe it is enough to change the entry values, but stick with the int
enum type?
I don't have a particular example where the different backing types would be problem though.
Having Nodes (or entries) like All
and None
sounds nice as well. However, All
might be better defined as foo | bar | ...
for that particular type.
We probably should put a [Flags]
Attribute on top, which affects the text rendering (object.ToString()
).
Related: #13
We even could switch to the 0, 1, 2
encoding in case there are too many entries. In this case, you suddenly lose the |
, the &
, and the ^
. Magic flip
You typically should anyways serialize the string representation, not the integer value. But you can't know what a user does with your enum.
So being able to define the enum entries and to be able to keep them at stable values is probably wanted.
So how about three modes:
-
Sequential
: C# style -
Flags
: like specified above; you get an error if there are too many entries. -
Manual
: allows tweaking each enum entry value and backing type
No magic flip involved. Now we can try to figure out what default is best. Ja, why not Flags?
I also took another read on this topic and found this thread quite helpful: https://stackoverflow.com/questions/7129976/flag-enums-without-power-of-two-values
so, my assumption, that |
, &
an ^
only work when the values are powerOfTwo seems right.
and there's also this case, where combinations are quite nice:
[Flags]
public enum FilePermissions
{
None = 0,
Read = 1,
Write = 2,
Execute = 4,
ReadWrite = 3, // Read | Write,
ReadWriteExecute = 7 // Read | Write | Execute
}
(i'm not sure if one even has to define the last 2 entries in the enum, since they can be inferred implicit)
i also like the auto switch, which is the combination of your proposed Sequential
and Flags
:
- use Flags as long thhere are less that 64 entries
- switch to sequential if there's more
- issue a Warning in that case
wouldn't that be a nice default with all the possible benefits (until more than 64)?
on the other hand, when reading those Guidelines for Flags/Enums, i could also think of 2 separate things: Flag
and Enum
. And then let the user decide if bitwise ops are wanted and choose accordingly. the system would then allow the bitwise operators only for Flags.
that clear distinction would be even more consistent i'd say. and the end-user story is also super simple:
- there's
Enum
which is a list of named entries where you can choose from. - if you want to have bitwise operations for such a thing, which might make sense in some cases,you can use
Flag
. This is an advancedEnum
that comes with extra Nodes|
,&
an^
, but its entries can't exceed 64
(i'm not sure if one even has to define the last 2 entries in the enum, since they can be inferred implicit)
if you define them you get a decent text representation of values 3 and 7.
5 is still Read | Execute
, but 3 is just ReadWrite
.
(I don't know exactly how it works. I guess sometimes there are different ways to represent one value. If you wouldn't have defined ReadWriteExecute, but instead ReadExecute=5, 7 could be rendered ReadWrite | Execute
or ReadExecute | Write
(...))
Anyways. It's sometimes handy to define shortcuts for combinations. And with them suddenly ReadWrite | Execute
makes sense (the |
node makes sense), even for values that are not powers of two.
regarding calling it Enum
or Flag
: yes the end-user story. There are always many ways to sell it in the end. But part of it is UI in a visual language.
I could imagine that the model (the source code) underneath should probably be a new Enum element, with a mode property that could have a value of:
-
Sequential
: C# style -
Flags
: As specified above; you get an error if there are too many entries. -
Manual
: allows tweaking each enum entry value and backing type -
MagicFlip
(since you brought it up again)
What about
-
Sequential
: C# default style -
Flags
: define backing type (ushort, uint or ulong), entry value pow2 incremented -
Explicit
: define backing type and each entry value ...
Flags
with max 64 entries sounds like defaulting to ulong. Thinking serialization and networking that would be pretty big. Also interfacing with (lowlevel) APIs could be handled very direct by just specifying the underlying bit/bytewidth (e.g. win.h with all those DWORD flags, Firmata/MIDI,...)
For library implementers it would probably be nice to use flags but expose them as simple enums to the user without redefining the type. Wrapping an API one often encounters non sequential flags and one might want to avoid having a library user e.g. sending a wrongly shifted bit to some piece of hardware.
-
Sequential
,Flags
,Explicit
. explicit sounds nice as well. -
Flags
backing type: are you saying that you want to specify the backing type manually here as well? what would be the default? would you only allow unsigned types (likeByte
,UInt16
,UInt32
,UInt64
)?UInt32
sounds like a good default to me.
however we should again check how the user typically would cast back and forth when working with ordinal values. I think that a hard cast results in a runtime exception if you accidentally used UInt32
instead of Int32
.
Flags with max 64 entries sounds like defaulting to ulong
oops. sorry. no, let's go for 32.
For library implementers it would probably be nice to use flags but expose them as simple enums to the user without redefining the type. Wrapping an API one often encounters non sequential flags and one might want to avoid having a library user e.g. sending a wrongly shifted bit to some piece of hardware.
can you rephrase that? what's the suggestion here? getting rid of some entries or the possibility to work on a bit level?
Maybe you just want to have |
, &
, ^
being internal
? That we probably could do via some checkbox... Yes.
default underlying type of flags: i guess it would make sense to follow c#. there are definitely flags with backing signed types around. so restricting to unsigned might be too harsh.
By default, the associated constant values of enum members are of type int; they start with zero and increase by one following the definition text order. You can explicitly specify any other integral numeric type as an underlying type of an enumeration type.
can you rephrase that? what's the suggestion here? getting rid of some entries or the possibility to work on a bit level?
Maybe you just want to have |, &, ^ being internal? That we probably could do via some checkbox... Yes. internal yes! a checkbox sounds nice, to hide the bit ops for the public facing side.
hm, i use the bitwise operations quite often in application patches. what is the rationale behind hiding those?
and also: this quest is not only about importing flags/enums but also about providing a possiblility for the users to define them. wouldn't it feel strange, when you for example have a MyEnums.vl referenced in your MyProject.vl and then can't access bit ops from inside MyProject.vl?
it's just about an option to be able to hide them. not by default.
usecase would be: LibWrapper.vl nicely wrapping functions like genericCall(payload, combinationOfFlags)
for the lib user as specificCallA(payload, simpleEnums), specificCallB(payload, simpleEnums),...
which in cases can be less error prone especially if flags are not sequential and using them without detailed knowledge which enum flags of the lib can be validly combined can cause havoc.
The lib implementor could of course implement 'normal' enus on the user facing side and use flags internally. that would require loads of casts though. that's the reason i was asking, if a simple ui option to mark them internal was possible.