Scientist in Perl

Saturday, Mar 15, 2025| Tags: Perl

DISCLAIMER: Image is generated using FREE version of ChatGPT.



Scientist in Perl


It was in the year 2017, I found out about the CPAN module Scientist and guess what?

It turned out be created by a very dear friend of mine, Lance Wicks.

I submitted few pull requests, mostly low hanging fruits, during that period and it was kindly accepted and merged.

You might ask me, what is Scientist?

Well, it is inspired by Ruby library primarily to assist in refactoring critical path of the code.

Something like below:


require "scientist"

class MyWidget
  def allows?(user)
    experiment = Scientist::Default.new "widget-permissions"
    experiment.use { model.check_user(user).valid? } # old way
    experiment.try { user.can?(:read, model) } # new way

    experiment.run
  end
end

So why talking about it now?

Actually I was going through my old notes and came across this.

I thought it is worth re-visiting and sharing it with fellow hackers.

The core idea behind the Scientist is to help you refactor critical path code with confidence.

I decided to install the latest version from CPAN.


$ cpanm -vS Scientist

Everything went smooth without any issues.

So am I good to go, right?

Well, the quick and easy way to test is to try running code as documented in the SYNOPSIS.


use feature qw(say);
use Scientist;
sub old_code {
  return 10;
}
sub new_code {
  return 20;
}
my $experiment = Scientist->new(
  experiment => 'MyTest',
  use => \&old_code,
  try => \&new_code,
);
my $answer = $experiment->run;
say "The number ten is $answer";
warn 'There was a mismatch between control and candidate'
  if $experiment->result->{'mismatched'};
say 'Timings:';
say 'Control code:   ', $experiment->result->{control}{duration}, ' microseconds';
say 'Candidate code: ', $experiment->result->{candidate}{duration}, ' microseconds';

When I ran the above code, I got the following error, ouch.


$ perl synopsis.pl
Can't locate object method "set_column_alias" via package "Test2::Compare::Delta"
(perhaps you forgot to load "Test2::Compare::Delta"?)

Did I miss something?

Not sure, I just installed the latest copy from CPAN without any issue..

What could go wrong?

I decided to look into the source code and found this line.

Quick fix would be to explicitly import Test2::Compare::Delta, right?

Something like below:

Pull Request

I fixed my local copy and I am good to go now.


$ perl synopsis.pl
The number ten is 10
There was a mismatch between control and candidate at synopsis.pl line 17.
Timings:
Control code:   9.5367431640625e-07 microseconds
Candidate code: 2.14576721191406e-06 microseconds

Before I move on, it would be nice if I submit the proposed change as pull request and wait for the review.

I would wait for few days and if I don’t hear from Lance Wicks then I’ll ping him through my private channel.

What’s next now as the code from SYNOPSIS ran without any error?

Let’s take a step back and come up with easy to follow simple use case.

I decided to extend the Scientist module as below to keep the interface clean and easy to follow.


package Experiment;

use v5.38;
use Moo;
extends 'Scientist';

sub publish($self) {
    my $result = $self->result;
    say "Control result    : ", $result->{observation}->{control};
    say "Control duration  : ", $result->{control}->{duration};
    say "Candidate result  : ", $result->{observation}->{candidate};
    say "Candidate duration: ", $result->{candidate}->{duration};
    say "Matched           : ", $result->{matched} ? 'Yes' : 'No';
    say "Diagnostic        : ";
    say $result->{observation}->{diagnostic};
}

Now we’ll try the happy path first.

Remember, the two most important factors we are interested in is return values and processing times.


package main;

sub control_implementation($input)   { return $input + $input }
sub candidate_implementation($input) { return $input * 2      }

my $experiment = Experiment->new(
    experiment => 'Example',
    use => sub { return control_implementation(5)   },
    try => sub { return candidate_implementation(5) },
);

$experiment->run;

Let’s run the code now:


$ perl experiment.pl
Control result    : 10
Control duration  : 1.9073486328125e-06
Candidate result  : 10
Candidate duration: 4.05311584472656e-06
Matched           : Yes
Diagnostic        :

How about the not so happy path?

We can easily mock that too.


package main;

sub control_implementation($input)   { return $input * $input }
sub candidate_implementation($input) { return $input ^ 2      }

my $experiment = Experiment->new(
    experiment => 'Example',
    use => sub { return control_implementation(5)   },
    try => sub { return candidate_implementation(5) },
);

$experiment->run;

Time to test the code:


$ perl experiment.pl
Control result    : 25
Control duration  : 1.9073486328125e-06
Candidate result  : 7
Candidate duration: 2.86102294921875e-06
Matched           : No
Diagnostic        :
+------+---------+----+-----------+
| PATH | CONTROL | OP | CANDIDATE |
+------+---------+----+-----------+
| [0]  | 7       | eq | 25        |
+------+---------+----+-----------+


Keep Hacking!!!

SO WHAT DO YOU THINK ?

If you have any suggestions or ideas then please do share with us.

Contact with me