Enum: oversight in typecasting? #24

Closed
sirrobert opened this Issue Sep 25, 2012 · 19 comments

7 participants

@sirrobert

Enums evaluate to expected strings in Str context and to expected ints in Int/Num context, but the Bool context is surprising. Consider the following from IRC (09/25/2012):

15:01 < sirrobert> r: enum E <A B C>;  for (E::A, E::B, E::C) { say $^a ?? "yep" !! "nope" }
15:01 <+p6eval> rakudo 16f22b: OUTPUT«nope␤yep␤yep␤»
15:01 < sirrobert> I find that *very* unexpected ... am I missing anything?
15:01 < sirrobert> (I think it's because the first one is getting evaluated as 0 because it's the first one)
15:02 < sirrobert> r: enum E <A B>; say so E::A; say so E::B;
15:02 <+p6eval> rakudo 16f22b: OUTPUT«False␤True␤»
15:03 < sirrobert> r: enum E <A B>; say E::A; say E::B;
15:03 <+p6eval> rakudo 16f22b: OUTPUT«A␤B␤»
15:03 < sorear> sirrobert: spec oversight, I'd say
15:03 < sirrobert> I think in bool context it should be True (or 0E0), and in Int context it should be 0;
15:03 < sorear> enums are specced to inherit from Int, and they don't have spec bool behavior
15:03 < sorear> so they inherit Int's bool behavior

This came up because I tested for the truthiness of an object property:

enum E <A B C>;
class A {
  has $.type;
  method something { 
    return $.type ?? "YES" !! "NO";
  }
}

my $a = A.new: type => E::A;

say $a.something;  # NO

A workaround is to test against whether the property is defined, but the behavior as it exists is surprising to me.

@xfix
Perl 6 member

Well, while current behavior is confusing, always saying True would be even more confusing - the return value would be useless. Usually first value is sort of exceptional, so it makes sense to do that. Also, in C language, first value of enums is falsy and others are truthy (not that this argument is good, but it's worth mentioning). Also, workarounds exist.

enum Awesome (Property => 0 but True);

But, I would like to hear opinions of people that actually work on Perl 6 specification.

@masak
Perl 6 member

With respect, I don't find the current behavior confusing. The original poster suggests that "A workaround is to test against whether the property is defined". To me that's not a workaround, that's the correct implementation.

I agree with GlitchMr that always returning True would be a step back. Basing the boolean value on the value of the enum makes much more sense to me — and from that follows that by default, the first enum will boolify to False. All good and according to various expectations.

worksforme

@sirrobert

Note: For the below, I'll refer to the E <A B C> of the original post for examples.

A couple of points from prior comments:

From GlitchMr (above)

Well, while current behavior is confusing, always saying True would be even more confusing - the return value would be useless.

I don't agree that the return value would be useless. It would represent whether the value was one of the elements enumerated by the list (in contrast to the return value of so E).

Further, the doc.perl6.org page explicitly describes the behavior I am advocating:

From http://doc.perl6.org/type/Enum#Bool

multi sub Bool(Mu) returns Bool:D
multi method Bool() returns Bool:D
Returns False on the type object, and True otherwise.

From which I infer that at least the implementor here would find the current behavior surprising.

I realize that S12 - Value Generation mentions the case of my Int enum day ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');:

Other than that, number valued enums act just like numbers, while string valued enums act just like strings. Fri.so is true because its value is 5 rather than 0. Sun.so is false.

but I would argue (against that part of the spec) that the position of the element doesn't intrinsically describe specialness, and that the cases in which you want to have "all true values except for one" are a special case that should be handled specially.

Cases like enum maybe <no yes> are not reasonably representative of the generic use case because "yes" and "no" carry an implicit dichotomy that is the Boolean case, expressed in English. There are infinitely more differentiations with meanings that are not binary.

@moritz
Perl 6 member

Note that Bool itself is an enum -- if all bool elements were to evaluated to True in boolean context, that would be very bad for Bool::False

Now a case could be made for enum values defaulting to a start other than zero, but that would be rather inconsistent with the rest of Perl 6.

So maybe it's best that you either stick to checking defined(), or manually set a non-zero first enum value; in other words leave the specs as is.

@sirrobert

As I mentioned above, I think Bool is an excellent special case of enums in general for which one value should be true and the other false.

Of course, I'll happily make my code work however I need to, but I still think conceptually this is a place in which the spec has a small bubble in it's otherwise largely smooth surface, so to speak =)

The strongest argument, in my opinion, is that the specialness of the boolean interpretation of the numerical value doesn't have any sensical representation in the nature of an enumeration (in general). This is because an enumeration isn't specifically a cardinal enumeration, generally (but, rather, ordinal). Beginning with 0 is an artifact of implementation (e.g. "C-style arrays use 0 to start indexing"), not because 0 has enumerical meaning.

All that said, I gladly defer to the will of the Benevolent Dictator for Life =)

@FROGGS
Perl 6 member

IMO if this should always return true for defined elems:
r: enum E <A B C>; for (E::A, E::B, E::C) { say $^a ?? "yep" !! "nope" }

then the following should do it too:
r: my @array = (0, 1, 2); for @array { say $^a ?? "yep" !! "nope" }

Because E::A is defined exactly like @array[0].

@xfix
Perl 6 member
# If this code works, it was written by GlitchMr.
# If not, I don't know who wrote it
enum Tristate <No Yes Ummm>;

But I agree, with this argument of days actually makes sense - it simply doesn't make sense to make any day falsy. Perhaps, it should think that every enum element is True unless otherwise specified (for example using explicit index). But at this point, it could break code - but does anybody even use those enums?

But thinking differently, in ^7 the first element is falsy - by design - you simply aren't supposed to use its elements in boolean context - it won't do what you expect. Do sane things, and it shouldn't hurt you.

The solution that could exist but won't happen ever (it's deprecated in Perl 5 - it was a mistake) is using 1 as array base.

@sirrobert

Maybe a solution in practice is to implement enums as something like:

enum Colors <
  INVALID
  Purple
  Green
  Blue
  # and so on
>;

(Though it doesn't solve the conceptual problem.)

@xfix
Perl 6 member

INVALID in enum sounds like ugly hack for me. I don't think that Perl 6 should have ugly hacks like that. But perhaps, enums are really flawed. Perhaps it would be good idea to make every num element truthy - the falsy first behavior is rarely useful.

@sirrobert

I agree about it's hackishness. Left as-is, it's the best solution I see to create a good map of the behavior-syntax-meaning triplex.

@moritz
Perl 6 member

Another suggestion is to make the default of an enum to be the same string as the name:

 enum A <b c>;
 # would now be the same as
 enum A (b => 'b', c => 'c');

Since all identifier strings are True in boolean context, that would solve this problem. Numeric enum values could still be provided with

 enum A (b => 0, 'c');

or similar constructs.

@xfix
Perl 6 member

@moritz: that sounds like a clever solution, but I see a problem with it - it breaks code using == for comparing enums.

Perhaps double valued enum would help? But, if double valued, why it wouldn't be simply but True.

@sirrobert

I'm a little out of my depth regarding implementation details, but how about having the value of the first enum be "0E0" instead of 0? That keeps compatibility with ==-style comparison, but lets the first value be both 0 and True.

If you want the first one to be a falsey 0, use Moritz's suggestion of enum A (b=>0, 'c') which would make A::b cast to False|0 and A::c cast to True|1.

@FROGGS
Perl 6 member

r: say so 0E0
rakudo 9af8f2: OUTPUT«False␤»

Do I miss something?

@japhb
@sirrobert
$ perl6
> so "0"
False
> so "0E0"
True
> 0 == "0"
True
> 

Not saying its' a great solution, but it does stuffs =)

@xfix
Perl 6 member

@sirrobert: You don't need that Perl 5 trick in Perl 6 - today you simply would type 0 but True.

@sirrobert

I don't think 0 but True works when the thing we're doing would affect the definition of True ...

@TimToady
Perl 6 member

I see no reason to duplicate the functionality of definedness in the boolean space, and plenty of reason to allow enums to distinguish a false value when that's a useful concept. So I'm ruling in favor of the status quo, for once. :-)

@TimToady TimToady closed this Sep 29, 2012
@lizmat lizmat added a commit that referenced this issue Feb 3, 2014
@lizmat lizmat Rename .ast method to .made
This makes much more sense when paired with "make".  It also makes the generic
examples of make that jnthn++ used in his

  http://jnthn.net/papers/2014-fosdem-perl6-today.pdf

(slide #24 and following) much more sensible to naive, or even experienced
Perl 6 users.  Perhaps the .ast method should be kept as a synonym for .made,
and may even have a check for AST-nodeness added, to give it additional
implicit documentational value in the Perl 6 internals.

Please note that this has bugged me since the Rakudo and NQP Internals workshop
last September.  It came back to me in full force after seeing jnthn++'s talk.
24373dc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment