Referring to the type of value of an enum in the enum's class methods

Let’s say I have the enums Foo and Bar

enum Foo : Int32
  # a lot of options
end

enum Bar : Int64
  # a lot of options
end

And for Int32 and Int64 I have defined:

struct Int32
  def self.foo (x)
    1
  end
end

struct Int64
  def self.foo (x)
    5
  end
end

Now I would like to be able to do something like:

foo= Foo.foo x

With the intention: Foo’s values are of type Int32, so I would want that somehow Int32.foo(x) gets called and if I would use Bar.foo(x) then Int64.foo(x) shall be called, and so on.

I guess I would have to define a method self.foo for struct Enum but I wouldn’t know how to refer to the enum’s value type in such a method

struct Enum
  def self.foo (x)
    __something_to_refer_to_the_type_of_the_value_of_the_enum__.foo x
    # basically this:  new(0).value.class.foo x
    # but I assume there is a better way than the new(0) workaround 
  end
end

With what would I have to replace __something_to_refer_to_the_type_of_the_value_of_the_enum__?

This doesn’t answer your question, but adding methods to core types is not recommended. I mean, you can do it. But I don’t see any strong reason to do things like that.

1 Like

I’m a bit confused on the purpose this whole thing is serving? Like is that idea that calling Int32.foo 10 returns a Foo instance with a value of 10?

1 Like

It looks like you want the #value method to get the integer value out of the enum instance.

enum AAA : Int32
  Aaa
  Bbb
  Ccc
  
  def foo
    value.foo
  end
end

struct Int32
  def foo
    "foo#{self}"
  end
end

p AAA::Aaa.foo # => "foo0"
p AAA::Bbb.foo # => "foo1"
p AAA::Ccc.foo # => "foo2"

But as @asterite said, avoid adding new methods to built-in types, especially primitive types. That will cause issues down the line.

1 Like

I just simplified it a bit in my first example code, as it seemed to make the code just unnecessary complicated for what 'm trying to ask.

What I try to do achieve is the same as this (working) code would do:

struct Enum
  def self.foo (x)
    from_value values[0].value.class.foo x
    # x is not an integer at all, but
    # Int32.foo(x), Int64.foo(x), etc will return values of Int32, Int64, etc
  end
end

The workaround values[0].value.class works, but I would imagine there must be some way, which would be more straightforward, to address the enum type’s value’s type.

Thanks. But sadly it’s not what I try to achieve (which would happen already before there is even an instance of AAA, basically I need it to figure out the value AAA shall get initiated with).

I originally used some pseudo enums (custom classes, which did behave somewhat like enums). But I’m trying to replace it with real enums (as Crystal’s enums seem to be quite awesome). And while new methods for built-in types might be to avoid, I guess they will be preferable over my pseudo enums which I used in the first place.

No, I get what you’re wanting to do, but it’s still not clear to me why. Like do your enum’s value actually represent something? If not then I’m doubtful that’s what you actually want to use. Just feels weird to go from a number, call an enum class method, that calls a monkey patched class method on specific number types, just to get another number? Could you share a bit more on what you’re using this for? May be a simpler way to go about it.

But to answer your question, pretty sure you could just do typeof(x).foo x. E.g.

struct Enum
  def self.foo(x)
    pp typeof(x).foo x
  end
end
 
Enum.foo 10
Enum.foo 10_i64
1 Like

The x in my example is most of the time a socket (and sometimes a buffer and in rare occasions a pseudo socket which just holds an infinite amount of ‘0’ bytes). The integers I retrieve from x are (most of the time) 7bit encoded, but some are just “normal” (little endian with the length of the type), and to deal with them I have the deserialisation done via TargetType.foo(x) (or actually from instead of foo, and I’m fully happy with the resulting code, like in Int64.from host.socket).

Some fo the transferred integers represent enums, and I end up wanting to do @someEnum= SomeEnum.from socket; of course I could do @someEnum= SomeEnum.from_value Int64.from socket but this would mean I would repeat the type Int64 even though it’s already known that enum SomeEnum : Int64. And as the enum is (and needs to be) typed already I want to avoid to type it anywhere else again.

Okay cool, this helps a lot I think. Because you know that each enum should handle a specific integer type. I think you could do something like:

Enum SomeEnum
  def self.from(socket) : self
    self.from_value Int64.from socket
  end
end

Then you can just do SomeEnum.from socket and call it a day? Although this of course would not handle a more generic API of defining something on Enum itself that handles returning one or the other enum types based on the type it’s defined as.

There was a PR to add Enum.base_type: Document how to change base type of an enum by Blacksmoke16 · Pull Request #9803 · crystal-lang/crystal · GitHub. But wasn’t really worth adding. Although you could leverage some compiler typeof magic to do like:

struct Enum
  def self.from(socket)
    self.from_value typeof(self.values.first.value).from socket
  end
end

This would return the type of the enum via some magic that doesn’t actually result in an array access or anything.

1 Like

Your suggestion for typeof magic, looks almost exactly like my current workaround.
The only real difference is that my workaround uses

values.first.value.class instead of
typeof(values.first.value)

Is there in this specific context one preferable over the other?

Yes, Carcin illustrates it well. When you use values.first.value.class that’s actually calling those methods and creating an array and such. Whereas typeof(values.first.value) does not actually do any of that. I.e. which is why first is only printed once in the playground link.

2 Likes

whereas typeof(values.first.value) does not actually do any of that

:open_mouth: this I didn’t know at all. I’m glad I asked (and you replied). Thanks!