The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Math::Formula - expressions on steriods

SYNOPSIS

my $formula = Math::Formula->new('size', '42k + 324', %options);
my $size    = $formula->evaluate;

my $context = Math::Formula::Context->new(name => 'example');
$context->add( { size => '42k', header => '324', total => 'size+header' });
my $total   = $context->value(total);

my $formula = Math::Formula->new(size => \&own_sub, %options);

DESCRIPTION

WARNING: This is not a programming language: it lacks control structures like loops and blocks. This can be used to get (very) flexible configuration files for your program.

What makes Math::Formula special? Zillions of expression evaluators have been written in the past. The application where this module was written for has special needs. Thereefore, this expression evaluator can do things which are usually hidden behind library calls.

For instance, where are many types which you can use to calculate directly (examples far below on this page)

true and false               # real booleans
"abc"  'abc'                 # the usual strings
7  89k  5Mibi                # integers with multiplier support
=~ "c$"                      # regular expressions and patterns
like "*c"                    # pattern matching
2023-02-18T01:28:12+0300     # date-times
2023-02-18                   # dates
01:18:12                     # times
P2Y3DT2H                     # duration
name                         # outcome of other expressions
#unit.owner                  # fragments (namespaces)
file.size                    # attributes
(1 + 2) * 3                  # parenthesis

With this, your expressions can look like this:

my_age   = (#system.now.date - 1966-05-04).years
is_adult = my_age >= 18

Expressions can refer to values computed by other expressions. The results are cached within the context. Also, external objects can maintain libraries of formulas or produce compatible data.

Why do I need it? My application has many kinds of configurable rules, often with dates and durations in it, to arrange processing. Instead of fixed, processed values in my configuration, each line can now be a smart expression. Declarative programming.

METHODS

Constructors

Math::Formula->new($name, $expression, %options)
-Option    --Default
 expression  <required>
 name        <required>
 returns     undef
expression => $expression

The expression is usually a (utf8) string, which will get parsed and evaluated on demand.

As special hook, you may also provide a CODE as $expression. This will be called as

$expression->($context, $this_formula, %options_to_evaluate);

Optimally, the expression returns any Math::Formula::Type object. Otherwise, autodetection kicks in. More details below in "CODE as expression" in Math::Formula::Context.

name => $name

The expression need a name, to be able to produce desent error messages. But also to be able to cache the results in the "Context". Expressions can refer to each other via this name.

returns => $type

Enforce that the type produced by the calculation of this $type. Otherwise, it may be different when other people are permitted to configure the formulas... people can make mistakes.

Accessors

$obj->expression()

Returns the expression string, which was used at creation.

$obj->name()

Returns the name of this expression.

$obj->returns()

Set when the expression promisses to produce a certain type.

$obj->tree($expression)

Returns the Abstract Syntax Tree of the $expression. Some of the types are only determined at the first run, for optimal laziness.

Running

$obj->evaluate( [ $context, %options ] )

Calculate the value for this expression given the $context. The Context groups the expressions together so they can refer to eachother. When the expression does not contain Names, than you may go without context.

-Option--Default
 expect  <any ::Type>
expect => %type

When specified, the result will be of the expected $type or undef. This overrules new(returns). Without either, the result type depends on the evaluation of the expression.

$obj->toType($data)

Convert internal Perl data into a Math::Formula internal types. Sometimes this guess cannot go wrong, in other cases a small mistake is not problematic.

In a small number of cases, auto-detection may break: is 'true' a boolean or a string? Gladly, all types will be cast into a string when needed; a wrong guess without consequences. It is preferred that your CODE expressions return explicit types.

See Math::Formula::Context/"CODE as expression" for details.

DETAILS

Formulas

explaining types

Let's start with a large group of related formulas, and the types they produce:

birthday: 1966-04-05      # DATE
os_lib: #system           # external OBJECT
now: os_lib.now           # DATETIME 'now' is an attribute of system
today: now.date           # DATE 'date' is an attribute of DATETIME
alive: today - birthday   # DURATION
age: alive.years          # INTEGER 'years' is an attr of DURATION

# this can also be written in one line:

age: (#system.now.date - 1966-04-05).years

Or some backup configuration lines:

backup_needed: #system.now.day_of_week <= 5    # Monday = 1
backup_start: 23:00:00
backup_max_duration: PT2H30M
backup_dir: "/var/tmp/backups"
backup_name: backup_dir ~ '/' ~ "backup-" ~ weekday ~ ".tgz"

The application which uses this configuration, will run the expressions with the names has listed. It may also provide some own formulas, fragments, and helper methods.

Operators

As prefix operator, you can use not, -, + on applicable data types. The # (fragment) and . (attributes) prefixes are weird cases: see Math::Formula::Context.

Operators work on explicit data types. Of course, you can use parenthesis for grouping.

The infix operators have the following priorities: (from low to higher, each like with equivalent priority)

LTR       or   xor
LTR       and
NOCHAIN	<    >    <=   ==   !=   <=>   # numeric comparison
NOCHAIN	lt   gt   le   eq   ne   cmp   # string comparison
LTR       +    -    ~
LTR       *    /    %
LTR       =~   !~   like  unlike         # regexps and patterns
LTR       #    .                         # fragments and attributes

The first value is a constant representing associativety. Either the constant LTR (compute left to right), RTL (right to left), or NOCHAIN (non-stackable operator).

Comparison operators

Some data types support numeric comparison (implement <=>, the spaceship operator), other support textual comparison (implement cmp ), where also some types have no intrinsic order.

The <=> and cmp return an integer: -1, 0, or 1, representing smaller, equal, larger.

=num  =text
  <     lt      less than/before
  <=    le      less-equal
  ==    eq      equal/the same
  !-    ne      unequal/different
  >=    ge      greater-equal
  >     gt      greater/larger

String comparison uses Unicode::Collate, which might be a bit expensive, but at least a better attempt to order utf8 correctly.

SEE ALSO

This module is part of Math-Formula distribution version 0.10, built on February 24, 2023. Website: http://perl.overmeer.net/CPAN/

LICENSE

Copyrights 2023 by [Mark Overmeer <markov@cpan.org>]. For other contributors see ChangeLog.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See http://dev.perl.org/licenses/