On Thu, Dec 11, 2003 at 02:48:06PM +0100, Stéphane Payrard wrote: : Hi, : : I don't remember anything about enums and bitenums in the : apocalypses. This is probably not very difficult to roll out : something using macros but I feel that should belong to the : standard language.
[Warning: speculation ahead.] I've been thinking that enums might just be subtypes of roles/properties. After all, when you say 0 but true it might really mean 0 but Boolean[1] whereas 1 but false means 1 but Boolean[0] That is, type boolean can be thought of as enum(false,true). So we might have a role declaration somewhere that says something like: role *Boolean[Bit ?$val] does Property { bit $.boolean = $val; } role *false does Boolean[0]; role *true does Boolean[1]; That's what the semantics might look like, but of course people would probably want an enum or bitenum wrapper macro for real code. No reason it couldn't be a standard macro though. Or even part of the grammar--we've never shied away from defining one construct in terms of another for teaching purposes. We define C<use> in terms of C<BEGIN> and C<require>, and C<require> in terms of C<do>. Perl 6 will do more of that, because it makes things easy to learn. A lot of this is negotiable, but I think it's a feature that you can't have an enum without it being associated with a unique type name/property. It's also appealing to me that enum "values" live in the type namespace, since they're really subtypes (that is, types that restrict values ranges of "real" types). There's nothing says a subtype has to have only one value: role Bit[Int ?$val] does Int[0|1]; Of course, I'm handwaving a lot here by using a junction. For integer subtypes you'd typically want a range: role Byte[Int ?$val] does Int[0..255]; That implies that the argument to Int[] has to be able to take a range. I confess I don't know what the real declaration of "Int" looks like yet. Certainly the first arg isn't simply an "Int". That only works for enums. The arg has to represent some kind of generic constraint. Pity the optimizer... Anyway, this all implies that use of a role as a method name defaults to returning whether the type in question matches the subtype. That is, when you say $foo.true it's asking whether the Boolean property fulfills the true constraint. When you say $bar.red it's asking whether the Color property fulfills the red constraint. I suppose when you say $baz.Byte it's asking whether the Int property fulfills the Byte constraint, but that's getting kind of strange. Another implication is that, if properties are subtypes, we can't use the same name as a cast method. Since $baz.Byte only returns true or false, we'd need something like (yuck) $baz.asByte Since class names are valid as bare names, perhaps we can improve that to $baz.as(Byte) (That doesn't mean you have to write an "as" method that contains a switch. More likely "as" is a multi method within the class of $baz.) Another possibility is that .Byte would have to be context sensitive. In which case we have the small problem that if $foo.boolean { print "true" } else { print "false" } prints "true" even if $foo is 0. You'd have to say if +$foo.boolean { print "true" } else { print "false" } to force a numeric context on the type. (Or use $foo.true.) Alternately, we use the type name as a cast always, and distinguish the boolean test: if $baz.does(Byte) { my $byte = $baz.Byte } I kinda like that. Since "does" is a generalization of "isa", we probably need to generalize smart matching to test "does" rather than "isa" for class/type names. On the other hand, it'd be a major pain to have to write $foo.does(true). I suppose a cast could return undef if it fails, which means that $foo.true would return 1 or undef. Except $foo.false would return 0 or undef. Urg. I suspect the right solution from a linguistic point of view is to make it context sensitive but also make it easy to disambiguate. Explicitly: $bar.does(Color) # does $bar know how to be a Color? $bar.as(Color) # always cast to Color Implicitly boolean: $bar ~~ Color # $bar.does(Color) ?$bar.Color # $bar.does(Color) if $bar.Color # if $bar.does(Color) Implicitly non-boolean: +$bar.Color # +$bar.as(Color) ~$bar.Color # ~$bar.as(Color) $($bar.Color) # $($bar.as(Color)) @($bar.Color) # @($bar.as(Color)) Then $foo.true and $foo.false work as expected. Still have to be careful about ?$foo.boolean though. Interestingly, we have $foo = $bar.Color || 0xfff; that is equivalent to $foo = $bar.does(Color) ?? $bar.as(Color) :: 0xfff; which means $foo could validly end up 0. Of course, // gets there by a different route: $foo = $bar.Color // 0xfff; means $foo = defined($bar.as(Color)) ?? $bar.as(Color) :: 0xfff; I think I'm happy with that. Maybe. Larry