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::Context - calculation context

SYNOPSIS

my $context = Math::Formula::Context->new();

DESCRIPTION

Like in web template systems, evaluation of expressions can be effected by the computation context which contains values. The Context object manages these values: it runs the right expressions.

METHODS

Constructors

Math::Formula::Context->new(%options)

Many of the %options make sense when this context is reloaded for file.

-Option --Default
 formula  []
formula => $form|ARRAY

One or more formula, passed to add().

Attributes

$obj->config()

Returns an MF_OBJECT which contains all information other expressions can use about the active context (or fragment).

$obj->name()

Contexts are required to have a name. Usually, this is the name of the fragment as well.

Formula and Fragment management

$obj->add(LIST)

Add one or more items to the context.

When a LIST is used and the first argument is a name, then the data is used to create a $formula or fragment (when the name starts with a '#').

Otherwise, the LIST is a sequence of prepared formulas and fragments, or a HASH with

example: :

  $context->add(wakeup => '07:00:00', returns => 'MF::TIME');
  
  my $form = Math::Formula->new(wakeup => '07:00:00', returns => 'MF::TIME');
  $context->add($form, @more_forms, @fragments, @hashes);
  
  my %library = (
    breakfast => 'wakeup + P2H',
	to_work   => 'PT10M',    # mind the 'T': minutes not months
    work      => [ 'breakfast + to_work', returns => 'MF::TIME' ],
	#filesys  => $fragment,
  );
  $context->add($form, \%library, $frag);

#XXX example with fragment

$obj->addFormula(LIST)

Add a single formula to this context. The formula is returned.

example:

$context->addFormula($form);            # already created somewhere else
$context->addFormula(wakeup => $form);  # register under a (different) name
$context->addFormula(wakeup => '07:00:00', returns => 'MF::TIME');
$context->addFormula(wakeup => [ '07:00:00', returns => 'MF::TIME' ]);
$obj->addFragment( [$name], $fragment )

A $fragment is simply a different Context. Fragments are addressed via the '#' operator.

$obj->evaluate($name, %options)

Evaluate the expresion with the $name. Returns a types object, or undef when not found.

$obj->formula($name)

Returns the formula with this specified name.

$obj->fragment($name)

Returns the fragment (context) with $name. This is not sufficient to switch between contexts, which is done during execution.

$obj->run($expression, %options)

Singleshot an expression: the expression will be run in this context but not get a name. A temporary Math::Formula object is created and later destroyed. The %options are passed to Math::Formula::evaluate().

-Option--Default
 name    <caller's filename and linenumber>
name => $name
$obj->value($expression, %options)

First run the $expression, then return the value of the returned type object. All options are passed to run().

DETAILS

Creating an interface to an object

For safity reasons, the formulars can not directly call methods on data objects, but need to use a well defined interface which hides the internals of your program. Some (Perl) people call this "inside-out objects".

With introspection, it would be quite simple to offer access to, for instance, a DateTime object which implements the DATETIME logic. This would, however, open a pit full of security and compatibility worms. So: the DATETIME object will only offer a small set of attributes, which produce results also provided by other time computing libraries.

The way to create an interface looks: (first the long version)

use Math::Formula::Type;
my $object    = ...something in the program ...;
sub handle_size($$%)
{   my ($context, $expr, %args) = @_;
    MF::INTEGER->new($object->compute_the_size);
}

my $name      = $object->name;  # f.i. "file"
my $interface = Math::Formala::Context->new(name => $name);
$interface->addAttribute(size => \&handle_size);
$context->addFragment($interface);

my $expr   = Math::Formula->new(allocate => '#file.size * 10k');
my $result = $expr->evaluate($context, expect => 'MF::INTEGER');
print $result->value;

Of course, there are various simplifications possible, when the calculations are not too complex:

my $filename  = '...';
my $fragment = Math::Formula::Context->new(name => 'file',
  attributes => {
    name     => $filename
    size     => sub { MF::INTEGER->new(-s $filename) },
    is_image => 'name =~ "*.{jpg,png,gif}"',
  });
$context->addAttribute(allocate => '#file.size * 10k');
print $context->value('#file.allocate');

In above example, the return type of the CODE for size is explicit: this is the fastest and safest way to return data. However, it can also be guessed:

size     => sub { -s $filename },

For clarity: the three syntaxes:

.name           an attribute to the context
allocate        a formula in the context
allocate.abs    an attribute of the expression result
#file           interface to an object, registered in the context
#file.size      an attribute to an object
#filesys.file(name).size   file(name) produces an object

CODE as expression

It should be the common practice to use strings as expressions. Those strings get tokenized and evaluated. However, when you need calculations which are not offered by this module, or need connections to objects (see fragments in Math::Formula::Context), then you will need CODE references as expression.

The CODE reference returns either an explicit type or a guessed type. When the type is explicit, you MUST decide whether the data is a "token" (in normalized string representation) or a "value" (internal data format).

Math::Formula's internal types are bless ARRAYs with (usually) two fields. The first is the token, the second the value. When the token is known, but the value is needed, the token will get parsed. And vice versa: the token can be generated from the value when required.

Some examples of explicit return object generation:

my $int = MF::INTEGER->new("3k", undef);  # token 3k given
my $int = MF::INTEGER->new("3k");         # same
say $int->token;  -> 3k
say $int->value;  -> 3000                 # now, conversion was run

my $dt  = DateTime->now;
my $now = MF::DATETIME->new(undef, $dt);  # value is given
my $dt2 = $now->value;                    # returns $dt
say $now->token;  -> 2032-02-24T10:00:15+0100

See Math::Formula::Type for detailed explanation for the types which can be returned. These are the types with examples for tokens and values:

MF::BOOLEAN   'true'            1        # anything !=0 is true
MF::STRING    '"tic"'           'tic'    # the token has quotes!
MF::INTEGER   '42'              42
MF::FLOAT     '3.14'            3.14
MF::DATETIME  '2023-...T09:...' DateTime-object
MF::DATE      '2023-02-24+0100' DateTime-object
MF::TIME      '09:12:24'        some HASH
MF::DURATION  'P3Y2MT12M'       DateTime::Duration-object
MF::NAME      'tac'             'tac'
MF::PATTERN   '"*c"'            qr/^.*c$/
MF::REGEXP    '"a.b"'           qr/^a.b$/
MF::FRAGMENT  'toe'             ::Context-object

When you decide to be lazy, Math::Formula will attempt to auto-detect the type. This is helped by the fact that operator will cast types which they need, for instance FLOAT to INTEGER or the reverse.

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/