Files
devops/perl/Examples/Chap9/Value.pm
2025-09-17 16:08:16 +08:00

292 lines
7.1 KiB
Perl

###
### Value.pm
###
## Chapter 9 section 4.2
package Value;
my %op = ("add" =>
{
"FEATURE,FEATURE" => 'add_features',
"FEATURE,CONSTANT" => 'add_feature_con',
"FEATURE,TUPLE" => 'add_feature_tuple',
"TUPLE,TUPLE" => 'add_tuples',
"TUPLE,CONSTANT" => undef,
"CONSTANT,CONSTANT" => 'add_constants',
NAME => "Addition",
},
"mul" =>
{
NAME => "Multiplication",
"FEATURE,CONSTANT" => 'mul_feature_con',
"TUPLE,CONSTANT" => 'mul_tuple_con',
"CONSTANT,CONSTANT" => 'mul_constants',
},
);
sub op {
my ($self, $op, $operand) = @_;
my ($k1, $k2) = ($self->kindof, $operand->kindof);
my $method;
if ($method = $op{$op}{"$k1,$k2"}) {
$self->$method($operand);
} elsif ($method = $op{$op}{"$k2,$k1"}) {
$operand->$method($self);
} else {
my $name = $op{$op}{NAME} || "'$op'";
die "$name of '$k1' and '$k2' not defined";
}
}
sub negate { $_[0]->scale(-1) }
sub reciprocal { die "Nonlinear division" }
package Value::Constant;
@Value::Constant::ISA = 'Value';
sub new {
my ($base, $con) = @_;
my $class = ref $base || $base;
bless { WHAT => $base->kindof,
VALUE => $con,
} => $class;
}
sub kindof { "CONSTANT" }
sub value { $_[0]{VALUE} }
sub scale {
my ($self, $coeff) = @_;
$self->new($coeff * $self->value);
}
sub reciprocal {
my ($self, $coeff) = @_;
my $v = $self->value;
if ($v == 0) {
die "Division by zero";
}
$self->new(1/$v);
}
sub add_constants {
my ($c1, $c2) = @_;
$c1->new($c1->value + $c2->value);
}
sub mul_constants {
my ($c1, $c2) = @_;
$c1->new($c1->value * $c2->value);
}
package Value::Tuple;
@Value::Tuple::ISA = 'Value';
sub kindof { "TUPLE" }
sub new {
my ($base, %tuple) = @_;
my $class = ref $base || $base;
bless { WHAT => $base->kindof,
TUPLE => \%tuple,
} => $class;
}
sub components { keys %{$_[0]{TUPLE}} }
sub has_component { exists $_[0]{TUPLE}{$_[1]} }
sub component { $_[0]{TUPLE}{$_[1]} }
sub to_hash { $_[0]{TUPLE} }
sub scale {
my ($self, $coeff) = @_;
my %new_tuple;
for my $k ($self->components) {
$new_tuple{$k} = $self->component($k)->scale($coeff);
}
$self->new(%new_tuple);
}
sub has_same_components_as {
my ($t1, $t2) = @_;
my %t1c;
for my $c ($t1->components) {
return unless $t2->has_component($c);
$t1c{$c} = 1;
}
for my $c ($t2->components) {
return unless $t1c{$c};
}
return 1;
}
sub add_tuples {
my ($t1, $t2) = @_;
croak("Nonconformable tuples") unless $t1->has_same_components_as($t2);
my %result ;
for my $c ($t1->components) {
$result{$c} = $t1->component($c) + $t2->component($c);
}
$t1->new(%result);
}
sub mul_tuple_con {
my ($t, $c) = @_;
$t->scale($c->value);
}
package Intrinsic_Constraint_Set;
sub new {
my ($base, @constraints) = @_;
my $class = ref $base || $base;
bless \@constraints => $class;
}
sub constraints { @{$_[0]} }
sub apply {
my ($self, $func) = @_;
my @c = map $func->($_), $self->constraints;
$self->new(@c);
}
sub qualify {
my ($self, $prefix) = @_;
$self->apply(sub { $_[0]->qualify($prefix) });
}
sub union {
my ($self, @more) = @_;
$self->new($self->constraints, map {$_->constraints} @more);
}
package Synthetic_Constraint_Set;
sub new {
my $base = shift;
my $class = ref $base || $base;
my $constraints;
if (@_ == 1) {
$constraints = shift;
} elsif (@_ % 2 == 0) {
my %constraints = @_;
$constraints = \%constraints;
} else {
my $n = @_;
require Carp;
Carp::croak("$n arguments to Synthetic_Constraint_Set::new");
}
bless $constraints => $class;
}
sub constraints { values %{$_[0]} }
sub constraint { $_[0]->{$_[1]} }
sub labels { keys %{$_[0]} }
sub has_label { exists $_[0]->{$_[1]} }
sub add_labeled_constraint {
my ($self, $label, $constraint) = @_;
$self->{$label} = $constraint;
}
sub apply {
my ($self, $func) = @_;
my %result;
for my $k ($self->labels) {
$result{$k} = $func->($self->constraint($k));
}
$self->new(\%result);
}
sub qualify {
my ($self, $prefix) = @_;
$self->apply(sub { $_[0]->qualify($prefix) });
}
sub scale {
my ($self, $coeff) = @_;
$self->apply(sub { $_[0]->scale_equation($coeff) });
}
sub apply2 {
my ($self, $arg, $func) = @_;
my %result;
for my $k ($self->labels) {
next unless $arg->has_label($k);
$result{$k} = $func->($self->constraint($k),
$arg->constraint($k));
}
$self->new(\%result);
}
## Chapter 9 section 4.2.5
sub apply_hash {
my ($self, $hash, $func) = @_;
my %result;
for my $c (keys %$hash) {
my $dotc = ".$c";
for my $k ($self->labels) {
next unless $k eq $c || substr($k, -length($dotc)) eq $dotc;
$result{$k} = $func->($self->constraint($k), $hash->{$c});
}
}
$self->new(\%result);
}
package Value::Feature;
@Value::Feature::ISA = 'Value';
sub kindof { "FEATURE" }
sub new {
my ($base, $intrinsic, $synthetic) = @_;
my $class = ref $base || $base;
my $self = {WHAT => $base->kindof,
SYNTHETIC => $synthetic,
INTRINSIC => $intrinsic,
};
bless $self => $class;
}
sub new_from_var {
my ($base, $name, $type) = @_;
my $class = ref $base || $base;
$base->new($type->qualified_intrinsic_constraints($name),
$type->qualified_synthetic_constraints($name),
);
}
sub intrinsic { $_[0]->{INTRINSIC} }
sub synthetic { $_[0]->{SYNTHETIC} }
sub scale {
my ($self, $coeff) = @_;
return
$self->new($self->intrinsic,
$self->synthetic->scale($coeff),
);
}
sub add_features {
my ($o1, $o2) = @_;
my $intrinsic = $o1->intrinsic->union($o2->intrinsic);
my $synthetic = $o1->synthetic->apply2($o2->synthetic,
sub { $_[0]->add_equations($_[1]) },
);
$o1->new($intrinsic, $synthetic);
}
sub mul_feature_con {
my ($o, $c) = @_;
$o->scale($c->value);
}
sub add_feature_con {
my ($o, $c) = @_;
my $v = $c->value;
my $synthetic = $o->synthetic->apply(sub { $_[0]->add_constant($v) });
$o->new($o->intrinsic, $synthetic);
}
sub add_feature_tuple {
my ($o, $t) = @_;
my $synthetic =
$o->synthetic->apply_hash($t->to_hash,
sub {
my ($constr, $comp) = @_;
my $kind = $comp->kindof;
if ($kind eq "CONSTANT") {
$constr->add_constant($comp->value);
} elsif ($kind eq "FEATURE") {
$constr->add_equations($comp->synthetic->constraint(""));
} elsif ($kind eq "TUPLE") {
die "Tuple with subtuple component";
} else {
die "Unknown tuple component type '$kind'";
}
},
);
$o->new($o->intrinsic, $synthetic);
}
1;