2

I am attempting to use arithmetic to evaluate the a function, which is written as a string:

#!/usr/bin/env perl

use strict;
use warnings FATAL => 'all';
use feature 'say';
use autodie ':default';

my $str = '0.203580063041053 * $x + -0.0273785448865449';
my $x = 3;
my $ans = eval $str;
say $ans;

the above code works, and gives the correct answer.

However, perlcritic says that the above code is best avoided: Expression form of "eval" at line 10, column 11. See page 161 of PBP. (Severity: 5)

I have read that section of Perl's best practices, but it is very long, and I don't see how it applies to very simple situations like what I'm doing.

What is a good way of evaluating functions then?

con
  • 5,767
  • 8
  • 33
  • 62
  • 2
    Maybe write a custom parser using e.g. [`Marpa::R2`](https://metacpan.org/pod/Marpa::R2) or [`Regexp::Grammars`](https://metacpan.org/pod/Regexp::Grammars) ? – Håkon Hægland Nov 29 '21 at 19:38
  • 1
    How do you get this code-in-a-string? What is the purpose of the whole thing? – zdim Nov 29 '21 at 19:39
  • @zdim the functions come from Algorithm::CurveFit. In the end, I wish to plot points with the function – con Nov 29 '21 at 19:42
  • 2
    It produces Perl code? Then yes, eval is appropriate – ikegami Nov 29 '21 at 20:13
  • @ikegami the function produces something like `0.203580063041053 * x + -0.0273785448865449` I edited `$x` into the string – con Nov 29 '21 at 20:16
  • 1
    Then it's an icky/hackish solution. Especially since the module that underlies Algorithm::CurveFit provides the equations as compiled Perl subs. – ikegami Nov 29 '21 at 21:33
  • 1
    To put it explicitly, the problem being flagged isn't using `eval` to evaluate Perl code, the problem is dealing with Perl code as data in the first place. – ikegami Nov 30 '21 at 14:35

2 Answers2

1

User @ikegami referred to another package in Algorithm::CurveFit, presumably Math::Symbolic.

The absolute simplest way that I can think of to write this is

#!/usr/bin/env perl

use strict;
use warnings FATAL => 'all';
use feature 'say';
use autodie ':default';
use Math::Symbolic ':all';

# https://metacpan.org/pod/Math::Symbolic
my $str = '0.203580063041053 * x + -0.0273785448865449';
my $x = 3;
my $tree = Math::Symbolic->parse_from_string($str);
my ($sub) = Math::Symbolic::Compiler->compile_to_sub($tree);
my $ans = $sub->($x);
say $ans;

this code is OK according to perlcritic, and does not use the eval loop.

I don't know why my ($sub) works, and my $sub doesn't work, I'd be grateful for an explanation.

con
  • 5,767
  • 8
  • 33
  • 62
  • 1
    `compile_to_sub` returns more than one value, so context is important here. `my ($sub)` is calling `compile_to_sub` in list context, so you're basically saying "I want the first item in the list". When you do this without params `my $sub = ...` you're calling `compile_to_sub` in scalar context, which will return to you the number of items in the list. So presumably in that case if you print `$sub` you'll see a `2`. – oalders Dec 06 '21 at 19:41
0

$str = '0.203580063041053 * $x + -0.0273785448865449' is in essence a function so I would use 'sub' to create the function like so:

#!/usr/bin/env perl

use strict;
use warnings FATAL => 'all';
use feature 'say';
use autodie ':default';

sub str{
       my $x = shift;
       0.203580063041053 * $x + -0.0273785448865449;
}
my $x = 3;
my $ans = eval { str($x) } ;
say $ans;

turtle
  • 417
  • 3
  • 11
  • thank you! but I'm looking for a way to do this without `eval`, as perlcritic says not to use `eval` – con Dec 01 '21 at 01:48
  • perl critic gives me 'source OK' when I ran the above code. You can certanly remove the eval block if its not needed. – turtle Dec 01 '21 at 16:05
  • 1
    @con: Perl::Critic complains about passing a string to `eval`. It will not complain about passing braces with Perl code inside to `eval`, which is what @turtle's answer does. This is an important distinction. I'm not advocating this answer, just clarifying. – Ed Sabol Dec 20 '21 at 17:43