Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inconsistent coercions for Bool #4924

Closed
p6rt opened this issue Dec 24, 2015 · 6 comments
Closed

inconsistent coercions for Bool #4924

p6rt opened this issue Dec 24, 2015 · 6 comments
Labels
LTA Less Than Awesome; typically an error message that could be better testneeded

Comments

@p6rt
Copy link

p6rt commented Dec 24, 2015

Migrated from rt.perl.org#127019 (status was 'resolved')

Searchable as RT127019$

@p6rt
Copy link
Author

p6rt commented Dec 24, 2015

From zefram@fysh.org

I'm not sure what identities we can expect coercions to obey in Perl 6,
but this combination of coercions sure looks surprising​:

True.^does(Numeric)
True
True.^isa(Int)
1
True.Numeric
1
True.Int
True

The two coercions are surprisingly inconsistent. In both cases the output
of the coercion isa Int, and the type to coerce to includes all of Int,
so one would think that, having found a satisfactory Int to coerce to,
the same Int would be the output of both coercions. But they differ.
Apparently True isn't Numeric enough to be the output of a coercion,
despite it being Int enough, when that's a more specific type.

Furthermore, in this case the input value is already of both of the
types to which it is requested to coerce. In this situation, it is
surprising that a coercion produces any result different from the input.
There is no work for the coercion to do.

The Numeric coercion shown above is also inconsistent with what is
obtained from a coercing type constraint​:

(sub (Numeric() $a) { $a })(True)
True

The coercion that looks inconsistent with the others and with common
sense is True.Numeric. If you're serious about Bool being a subclass
of Int, surely True.Numeric needs to return True. (Overall it would be
more sensible for Bool to have no particular relation to Int and Numeric,
though. The subclass arrangement is detrimental to type discrimination
and to correctness.)

-zefram

@p6rt
Copy link
Author

p6rt commented Aug 17, 2016

From @lizmat

Fixed with a4140a3 , tests needed

On 24 Dec 2015, at 09​:28, Zefram (via RT) <perl6-bugs-followup@​perl.org> wrote​:

# New Ticket Created by Zefram
# Please include the string​: [perl #​127019]
# in the subject line of all future correspondence about this issue.
# <URL​: https://rt-archive.perl.org/perl6/Ticket/Display.html?id=127019 >

I'm not sure what identities we can expect coercions to obey in Perl 6,
but this combination of coercions sure looks surprising​:

True.^does(Numeric)
True
True.^isa(Int)
1
True.Numeric
1
True.Int
True

The two coercions are surprisingly inconsistent. In both cases the output
of the coercion isa Int, and the type to coerce to includes all of Int,
so one would think that, having found a satisfactory Int to coerce to,
the same Int would be the output of both coercions. But they differ.
Apparently True isn't Numeric enough to be the output of a coercion,
despite it being Int enough, when that's a more specific type.

Furthermore, in this case the input value is already of both of the
types to which it is requested to coerce. In this situation, it is
surprising that a coercion produces any result different from the input.
There is no work for the coercion to do.

The Numeric coercion shown above is also inconsistent with what is
obtained from a coercing type constraint​:

(sub (Numeric() $a) { $a })(True)
True

The coercion that looks inconsistent with the others and with common
sense is True.Numeric. If you're serious about Bool being a subclass
of Int, surely True.Numeric needs to return True. (Overall it would be
more sensible for Bool to have no particular relation to Int and Numeric,
though. The subclass arrangement is detrimental to type discrimination
and to correctness.)

-zefram

@p6rt
Copy link
Author

p6rt commented Aug 17, 2016

The RT System itself - Status changed from 'new' to 'open'

@p6rt
Copy link
Author

p6rt commented Aug 17, 2016

From @zoffixznet

Tests added in Raku/roast@d0f8198

@p6rt
Copy link
Author

p6rt commented Aug 17, 2016

@zoffixznet - Status changed from 'open' to 'resolved'

@p6rt p6rt closed this as completed Aug 17, 2016
@p6rt
Copy link
Author

p6rt commented Sep 19, 2017

From @skids

On Wed, 17 Aug 2016 09​:03​:15 -0700, cpan@​zoffix.com wrote​:

Tests added in
Raku/roast@d0f8198

There is still the matter of Numeric() in a signature. However since​:

$ perl6 -e 'my Numeric() $a;'
===SORRY!=== Error while compiling -e
Coercion Numeric(Any) is insufficiently type-like to qualify a variable
at -e​:1
------> my Numeric() $a⏏;
  expecting any of​:
  constraint

... one wonders what the fate of Numeric() is.

WRT to the subject of what .Numeric/.Real/.Int/etc do (long screed
follows)​:

Here's how I construe the reasoning for the decision to
have True.Numeric/True.Real/True.Int behave as they now
do​:

Casts to the Numeric/Real *roles* are used in the arithmetic
core as a way to limit the number of candidates needed for ops.
So .Numeric is sort of a first stage to get classes that
can numify into any of Int/Num/Rat/Complex arithmetic, and
then later if broadening is needed, .Bridge is used to move
Int and Rat into Num arithmetic (though theoretically,
Int should move up through Rat and FatRat before getting
to Num... possibly it does in some cases.)

Trying to fit type calculus onto the arithmetic system is
fraught with challenges​: Binary numeric operations are supposed
to produce the broader of the involved types. However,
in the few places where there is a class/subclass relationship
between numeric types (e.g. int isa Int) it is the narrower
type which is the subclass. Meanwhile, enhancements to types
are also subclasses (e.g. IntStr isa Int).

Enums, horribly, happen fall into both categories​: they
are both a subset of Int and an enhancement to carry
identifier keys alongside a value.

Finally you have use cases both for defining narrower
numeric types and for defining broader cases. If you want
an "MyInt is Int" with a .even method, there's a good chance
you are going to want to be able to add 1 to it and get an
IntEven. But if you have a "subset of Int" you probably
want to be able to produce results from outside that subset...
you only want the things to which you specifically apply that
type to be subject to those constraints.

The current system is only good for the latter, and allows
you to swim with the class system in that case and use
MMD productively during that process.

For classes that do not have the Numeric role, the .Numeric
method allows them to shed their class when entering the
arithmetic regime. For classes that do have the .Numeric
role already, the method would be useless/ancillary... they
should never hit .Numeric calls in the arithmetic core
as they'll directly hit candidates of Numeric or more
specific types.

In the case of enums, where you have extra data attached,
it's reasonable for the .Numeric method to do essentially
what it does for non-Numeric classes... ditch your subclass
and become an instance of another (in this case, a parent)
class.

The current behavior allows enum and IntStr appear to behave
the same as a numifiable non-Numeric class, which is probably
the most common kind of class a user would implement, and
hence the behavior most often expected from .Numeric.

Now, a good case might be made that really Perl6 should have
named .Numeric something like .Numify so there was no
expectation for class-like coercive no-ops, but I think that
horse has left the barn.

This is more grating to FP adherents in the case of
.Int/.Rat/etc which are classes rather than roles, but in this
case there is also the additional practical use case of how one
should go about releasing a value from the constraints of a subset
type when that is what you want to do. Not that no-ops are
never a useful behavior, but they are not as useful as that.

BTW, I'd argue this should also work here where it doesn't,
currently​:

my $a = 5 but "foo"; $a.Numeric.Str.say; # Should probably say "5"

Something in the future needs to be done for the former case
a few paragraphs up, where you want a sticky enhancement
to a base Numeric class. In this case, if you want to avoid
writing a full set of arithmetic candidates and do not care
what type interim calculations are performed on, some role
defined in core or module space that provides candidates
that .Numeric, perform the op, then cast (or clone with tweaks)
final results back to the broader operand's class might be
sufficient (along with some system to decide what is broader).

For more complex things like logging/Proxy classes where interim
results need to be in your class, a prefab solution might have
to wait for macros.

Efficiency concerns will come up with any of that. I think the
idea of an "exact type smiley" that prevents subclass matches
(without using an optimizer/mmd unfriendly where clause) might help
here as well. In addition, we could define a ".Int​:E" method
(or whatever character the smiley ended up being), etc to do
what the current behavior does, which could be used to alleviate
type calculus guilt, since it would pretty explicitly be saying
"coerce into this exact type, don't care if you are already a
subclass."

@p6rt p6rt added LTA Less Than Awesome; typically an error message that could be better testneeded labels Jan 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LTA Less Than Awesome; typically an error message that could be better testneeded
Projects
None yet
Development

No branches or pull requests

1 participant