brian d foy
brian d foy

Reputation: 132896

How can I convert a Str to an Int only when it represents an integer?

In Perl 6 you can specify a type that a type can be coerced to. For example, you need an Int but get something else that can convert to an Int. This is handy when you don't want separate candidates for Int and Str where the string represents an integer value.

But, it seems that the conversion is a bit aggressive because the conversion not only changes the type but is willing to change the data. It's partly a problem of the conflation of changing types and an expected operation to truncate a number to an integer. Conceptually those are different ideas but they are intertwined in Str.Int (actually sandwiching a side trip to Numeric):

sub foo ( Int:D() $n ) { put "Got <$n> of type {$n.^name}" }
foo( 80 );     # Got <80> of type Int
foo( '99' );   # Got <99> of type Int
foo( 1.5 );    # Got <1> of type Int
foo( '1.5' );  # Got <1> of type Int

Trying to limit this to Str isn't any better:

sub foo ( Int:D(Str:D) $n ) { put "Got <$n> of type {$n.^name}" }
foo( '1.5' );  # Got <1> of type Int

I could make some adapters which seems the easiest to understand:

multi foo ( Int:D $n ) {
    put "Got <$n> of type {$n.^name}"
    }
multi foo ( Str:D $n where { $^n.Int == $^n.Numeric } ) {
    foo( $n.Int );
    }

foo( '1.5' );  # Cannot resolve ...

And I can probably come up with some subsets but that's not any more satisfying. So the trick is, can I coerce like this without changing the value (even if it changes representation)?


It turns out that this feature is broken and doesn't have a timeline for repair: RT 132980. Basically, the target type is not enforced. The docs are updated. My advice is to not use this at all.

Upvotes: 8

Views: 326

Answers (2)

Brad Gilbert
Brad Gilbert

Reputation: 34130

The way Int:D(Any) works in Rakudo is by creating a multi candidate that accepts Any, converts it to an Int, and uses the result to call your original subroutine.

If you instead do that yourself, you can have more control over how it works.

proto sub foo ( Int:D() $n ) {*}

multi sub foo ( Any:D $n ) {
  my $i = try $n.Numeric.narrow;

  if $i ~~ Int:D {

    samewith $i

  } else {

    X::TypeCheck::Binding::Parameter.new(

      # there are more arguments that should be added here
      got => $n,
      expected => Int:D(),

    ).throw

  }
}

multi sub foo ( Int:D $n ) { put "Got <$n> of type {$n.^name}" }

Upvotes: 7

Christoph
Christoph

Reputation: 169723

One possible signature would be

Numeric() $ where Int

or, restricting to strings,

Numeric(Str:D) $ where Int

Upvotes: 8

Related Questions