Experiments in Overloading
Tags: perl, moo, overload
Published: 2023-04-02 12:16:23

---
title: Experiments in Overloading
published: true
date: 2023-04-02 12:03:44 UTC
tags: Perl,Moo,overload,perl
canonical_url: https://toby.ink/blog/2023/04/02/experiments-in-overloading/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tysjgf50o964ejlhmg23.jpg
---

Let’s play with overloading a little.

A simple class:

```perl
  package Local::Overloaded {
    use Moo;
    
    has number => ( is => 'ro' );
    
    use overload '0+' => sub {
      my $self = shift;
      return $self->number;
    };
  }
```

And let’s test it:

```perl
  use Test2::V0;
  my $obj = Local::Overloaded->new( number => 42 );
  is( 0+$obj, 42 );
  done_testing;
```

This test fails.

Why?

We tend to think of `0+` as the way to “cast” a Perl variable to a number, so much so that the [overload](https://metacpan.org/pod/overload) pragma even calls the numeric overload “0+”. However it is of course actually an addition, and we haven’t overloaded the plus operator.

The simple solution is to just including the `fallback => true` option when overloading. This tells Perl to fill in as many missing overloaded operations it can based on the operations you’ve explicitly provided.

But I’m not interested in the simple solution. I’m interested in what we can do with the “0+” overload.

Let’s try rephrasing our test case:

```perl
  use Test2::V0;
  my $obj = Local::Overloaded->new( number => 42 );
  ok( $obj == 42 );
  done_testing;
```

This still fails. The equality operator isn’t overloaded either.

However, this one works:

```perl
  use Test2::V0;
  my $obj = Local::Overloaded->new( number => 42 );
  is( $obj, 42 );
  done_testing;
```

So how is Test2 comparing them?

The answer is that whenever the right hand side of an `is` comparison is a non-reference, Test2 compares them as strings. Specifically, it does this:

```
  "$left" eq "$right"
```

And for whatever reason, even though we didn’t set `fallback => true`, Perl will happily apply the numeric overload when an object is interpolated into a string and doesn’t have a string overload.

So let’s add some stringy overloading to our class:

```perl
  package Local::Overloaded {
    use Moo;
    use Lingua::EN::Numbers qw( num2en );
    
    has number => ( is => 'ro' );
    
    use overload (
      '0+' => sub {
        my $self = shift;
        return $self->number;
      },
      '""' => sub {
        my $self = shift;
        return num2en( $self->number );
      },
    );
}
```

Now our previously passing test fails:

```perl
  use Test2::V0;
  my $obj = Local::Overloaded->new( number => 42 );
  is( $obj, 42 );
  done_testing;
```

It is comparing “forty-two” and “42” as strings.

So this is where we stumble upon the safe way to cast to a number. And it’s still not `0+`.

```perl
  use Test2::V0;
  my $obj = Local::Overloaded->new( number => 42 );
  is( sprintf( '%d', $obj ), 42 );
  is( sprintf( '%s', $obj ), 'forty-two' );
  done_testing;
```

Passing the object to `sprintf( '%d' )` for integers or `sprintf( '%f' )` will actually use the numeric overload without complaining about `==` and `+` not being overloaded. This seems the most reliable way to cast an object to a number if it doesn’t have overload fallbacks enabled, or you’re not sure if it does.

The lesson here is that overloading can be weird in Perl, but using `fallback => true` makes it a lot less weird.

The other lesson is to use `sprintf( '%d' )` or `sprintf( '%f' )` to cast to a number instead of `0+`.