byteunit
byteunit

Reputation: 1063

Adding two enums that share some same identifiers

Is it possible to define two enums in raku that share the same identifiers?

For example, if I have the following code:

#!/usr/bin/raku
use v6;

enum Color <Red Blue>;
enum TrafficLight <Red Green>;

sub MAIN(
    Color:D :c(:$color)!, #= the color
    TrafficLight:D :t(:$traffic-light)!, #= the traffic-light
) {
    say "Selected $color, Selected $traffic-light"
}

you can see that here, the identifer Red is part of the enum color and the enum TrafficLight.

But when I execute this script, I get the Redeclaration-exception:

$ ./test.p6
Potential difficulties:
    Redeclaration of symbol 'Red'
    at /home/martin/mnt/release-notes/./scripts/test.p6:5
    ------> enum TrafficLight <Red Green>⏏;

Usage:
  ./scripts/test.p6 -c|--color=<Color> (Blue Red) -t|--traffic-light=<TrafficLight> (Green Red)

    -c|--color=<Color> (Blue Red)                    the color
    -t|--traffic-light=<TrafficLight> (Green Red)    the traffic-light

Interestingly, when I execute this script with the parameters -c=Blue and -t=Red, the output is that that I would expect:

$ ./test.p6 -c=Blue -t=Red
Potential difficulties:
    Redeclaration of symbol 'Red'
    at /home/martin/mnt/release-notes/./scripts/test.p6:5
    ------> enum TrafficLight <Red Green>⏏;
Selected Blue, Selected Red

But when I exeucte this script with the parameters -c=Red and -t=Green, it doesn't work at all and error-code 2 is returned (showing the help-message).

My questions are now:

Thanks

Upvotes: 9

Views: 183

Answers (2)

Brad Gilbert
Brad Gilbert

Reputation: 34130

If you only want the enum exported and not the values in the enum, you can use a constant with a do block.

constant Color = do {
  my enum Color <Red Blue>;
  Color
}

constant Traffic-Light = do {
  my enum Traffic-Light <Red Green>;
  Traffic-Light
}

By doing this you only have to access the values in the enum by its fully qualified name, or by hash access.

say Color::Red.raku;
say Traffic-Light::Red.raku;

say Color::{'Red'}.raku;
say Traffic-Light::{'Red'}.raku;

say Red; # ERROR: Undeclared name: Red
multi foo ( Color $c ){
  say "the color $c"
}
multi foo ( Traffic-Light::Red ){
  say "stop"
}
multi foo ( Traffic-Light::Green ){
  say "go"
}
multi foo ( Str $s ){
  samewith Traffic-Light::{$s} // Color::{$s}
}

foo Color::Red;  # the color Red
foo Color::Blue; # the color Blue

foo Traffic-Light::Red;   # stop
foo Traffic-Light::Green; # go

foo 'Green'; # go
foo 'Red';   # stop
foo 'Blue';  # the color Blue

Upvotes: 3

user0721090601
user0721090601

Reputation: 5346

The problem is that enum create symbols in their scope. Your code

enum Color <Red Blue>;
enum TrafficLight <Red Green>;

is basically doing

my \Color = Map.new(Red => 0, Blue => 1) does Enumeration;
my \Red  := Color<Red>;
my \Blue := Color<Blue>;

my \Traffic-Light = Map.new(Red => 0, Green => 1) does Enumeration;
my \Red   := Traffic-Light<Red>;
my \Green := Traffic-Light<Green>;

Thus you can see what generates the warning -- you can't create the symbol twice anymore than you can declare $x twice in the same scope. Nonetheless, the two enum classes still exist, and can create values from the string "Red". One solution I've used in this case is to create a package and call the enum inside the package: Enum

package Color        {  enum Enum <Red Blue>   }
package TrafficLight {  enum Enum <Red Green>  }

sub MAIN(
    Color::Enum:D        :c(:$color        )!, #= the color
    TrafficLight::Enum:D :t(:$traffic-light)!, #= the traffic-light
) {
    say "Selected $color, Selected $traffic-light"
}

If you want to match against the values, then you just say Color::Red or TrafficLight::Green, or if you store things in a module so that you can use, you could still use just Red or Green, just not in the same scope. So you could do:

sub MAIN(
    Color::Enum:D        :c(:$color        )!, #= the color
    TrafficLight::Enum:D :t(:$traffic-light)!, #= the traffic-light
) {
    say "Selected $color, Selected $traffic-light"

    { # new scope 
        use MyEnums::Color;
        given $color { 
            when Red   { ... }
            when Green { ... }
        }
    }

    { # separate new scope 
        use MyEnums::TrafficLight;
        ... 
    }
}

Upvotes: 11

Related Questions