Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the point of coercions like Int(Cool)?

Tags:

raku

The Perl 6 Web site on functions says

Coercion types can help you to have a specific type inside a routine, but accept wider input. When the routine is called, the argument is automatically converted to the narrower type.

sub double(Int(Cool) $x) {
    2 * $x
}

say double '21';    # 42
say double Any;     # Type check failed in binding $x; expected 'Cool' but got 'Any'

Here the Int is the target type to which the argument will be coerced, and Cool is the type that the routine accepts as input.

But what is the point for the sub? Isn't $x just an Int? Why would you restrict the caller to implement Cool for the argument?

I'm doubly confused by the example because Int already is Cool. So I did an example where the types don't share a hierarchy:

class Foo { method foomethod { say 'foomethod' } }
class Bar {}

class Quux is Foo {
# class Quux { # compile error
  method Bar { Bar.new }
}

sub foo(Bar(Foo) $c) {
  say $c.WHAT;    # (Bar)
  # $c.foomethod  # fails if uncommented: Method 'foomethod' not found for invocant of class 'Bar'
}

foo(Quux.new)

Here the invocant of foo is restricted to provide a Foo that can be converted to a Bar but foo cannot even call a method of Foo on $c because its type is Bar. So why would foo care that the to-be-coerced type is a Foo in the first place?

Could someone shed some light on this? Links to appropriate documentation and parts of the spec are appreciated as well. I couldn't find anything useful there.

like image 948
musiKk Avatar asked Jan 19 '16 10:01

musiKk


People also ask

Why is coercion important?

One of the clearest, most important uses of coercion has been understood to be the state's enforcement of law, either through direct uses of force or through punishments meted out to lawbreakers.

Can coercion be good?

Although its connotations are typically negative, it is arguable that coercion is not necessarily unethical in all cases. Insofar as coercion consists in the limiting of individual freedoms, there are instances in which coercion is prima facie permissible.

What is an example of coercion?

These actions may include extortion, blackmail, or even torture and sexual assault. For example, a bully may demand lunch money from a student where refusal results in the student getting beaten. In common law systems, the act of violating a law while under coercion is codified as a duress crime.

How does coercion work?

Type Coercion refers to the process of automatic or implicit conversion of values from one data type to another. This includes conversion from Number to String, String to Number, Boolean to Number etc. when different types of operators are applied to the values.


2 Answers

Update Having reviewed this answer today I've concluded I had completely misunderstood what @musiKk was getting at. This was revealed most clearly in @darch's question and @musiKk's response:

@darch: Or is your question why one might prefer Int(Cool) over Int(Any)? If that's the case, that would be the question to ask.

@musiKk: That is exactly my question. :)

Reviewing the many other answers I see none have addressed it the way I now think it warrants addressing.

I might be wrong of course so what I've decided to do is leave the original question as is, in particular leaving the title as is, and leave this answer as it was, and instead write a new answer addressing @darch's reformulation.

Specify parameter type, with no coercion: Int $x

We could declare:

sub double (Int $x) { ... } # Accept only Int. (No coercion.)

Then this would work:

double(42);

But unfortunately typing 42 in response to this:

double(prompt('')); # `prompt` returns the string the user types

causes the double call to fail with Type check failed in binding $x; expected Int but got Str ("42") because 42, while looking like a number, is technically a string of type Str, and we've asked for no coercion.

Specify parameter type, with blanket coercion: Int() $x

We can introduce blanket coercion of Any value in the sub's signature:

sub double (Int(Any) $x) { ... } # Take Any value. Coerce to an Int.

Or:

sub double (Int() $x)    { ... } # Same -- `Int()` coerces from Any.

Now, if you type 42 when prompted by the double(prompt('')); statement, the run-time type-check failure no longer applies and instead the run-time attempts to coerce the string to an Int. If the user types a well-formed number the code just works. If they type 123abc the coercion will fail at run-time with a nice error message:

Cannot convert string to number: trailing characters after number in '123⏏abc'

One problem with blanket coercion of Any value is that code like this:

class City { ... } # City has no Int coercion
my City $city;
double($city);

fails at run-time with the message: "Method 'Int' not found for invocant of class 'City'".

Specify parameter type, with coercion from Cool values: Int(Cool) $x

We can choose a point of balance between no coercion and blanket coercion of Any value.

The best class to coerce from is often the Cool class, because Cool values are guaranteed to either coerce nicely to other basic types or generate a nice error message:

# Accept argument of type Cool or a subclass and coerce to Int:
sub double (Int(Cool) $x) { ... }

With this definition, the following:

double(42);
double(prompt(''));

works as nicely as it can, and:

double($city);

fails with "Type check failed in binding $x; expected Cool but got City (City)" which is arguably a little better diagnostically for the programmer than "Method 'Int' not found for invocant of class 'City'".


why would foo care that the to-be-coerced type is a Foo in the first place?

Hopefully it's now obvious that the only reason it's worth limiting the coerce-from-type to Foo is because that's a type expected to successfully coerce to a Bar value (or, perhaps, fail with a friendly message).

Could someone shed some light on this? Links to appropriate documentation and parts of the spec are appreciated as well. I couldn't find anything useful there.

The document you originally quoted is pretty much all there is for enduser doc. Hopefully it makes sense now and you're all set. If not please comment and we'll go from there.

like image 109
raiph Avatar answered Oct 27 '22 23:10

raiph


What this does is accept a value that is a subtype of Cool, and tries to transform it into an Int. At that point it is an Int no matter what it was before.

So

sub double ( Int(Cool) $n ) { $n * 2 }

can really be thought of as ( I think this is how it was actually implemented in Rakudo )

# Int is a subtype of Cool otherwise it would be Any or Mu
proto sub double ( Cool $n ) {*}

# this has the interior parts that you write
multi sub double (  Int $n ) { $n * 2 }

# this is what the compiler writes for you
multi sub double ( Cool $n ) {
    # calls the other multi since it is now an Int
    samewith Int($n);
}

So this accepts any of Int, Str, Rat, FatRat, Num, Array, Hash, etc. and tries to convert it into an Int before calling &infix:<*> with it, and 2.

say double '  5  '; # 25
say double 2.5;     # 4
say double [0,0,0]; # 6
say double { a => 0, b => 0 }; # 4

You might restrict it to a Cool instead of Any as all Cool values are essentially required to provide a coercion to Int.

( :( Int(Any) $ ) can be shortened to just :( Int() $ ) )


The reason you might do this is that you need it to be an Int inside the sub because you are calling other code that does different things with different types.

sub example ( Int(Cool) $n ) returns Int {
    other-multi( $n ) * $n;
}

multi sub other-multi ( Int $ ) { 10 }
multi sub other-multi ( Any $ ) {  1 }

say example 5;   # 50
say example 4.5; # 40

In this particular case you could have written it as one of these

sub example ( Cool $n ) returns Int {
    other-multi( Int($n) ) * Int($n);
}

sub example ( Cool $n ) returns Int {
    my $temp = Int($n);
    other-multi( $temp ) * $temp;
}

sub example ( Cool $n is copy ) returns Int {
    $n = Int($n);
    other-multi( $n ) * $n;
}

None of them are as clear as the one that uses the signature to coerce it for you.


Normally for such a simple function you can use one of these and it will probably do what you want.

my &double = * * 2; # WhateverCode
my &double = * × 2; # ditto

my &double = { $_ * 2 };       # bare block
my &double = { $^n * 2 };      # block with positional placeholder
my &double = -> $n { $n * 2 }; # pointy block

my &double = sub ( $n ) { $n * 2 } # anon sub
my &double = anon sub double ( $n ) { $n * 2 } # anon sub with name

my &double = &infix:<*>.assuming(*,2); # curried
my &double = &infix:<*>.assuming(2);

sub double ( $n ) { $n * 2 } # same as :( Any $n )
like image 24
Brad Gilbert Avatar answered Oct 27 '22 22:10

Brad Gilbert