From: Matt S Trout Date: Sat, 10 Oct 2015 01:38:10 +0000 (+0000) Subject: dependency map up, some code running X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=efad53c4944d3825739a7a0416bd40331926c132;p=scpubgit%2FDX.git dependency map up, some code running --- diff --git a/lib/DX/Action/SetValue.pm b/lib/DX/Action/SetValue.pm new file mode 100644 index 0000000..7b40168 --- /dev/null +++ b/lib/DX/Action/SetValue.pm @@ -0,0 +1,32 @@ +package DX::Action::SetValue; + +use DX::Update::SetValue; +use DX::Class; + +has target_path => (is => 'ro', required => 1); + +has new_value => (is => 'ro', required => 1); + +has _update => (is => 'lazy'); + +sub dry_run { + my ($self, $hyp) = @_; + my ($scope, @events) = $self->_update->apply_to($hyp->scope); + return ( + $hyp->but( + actions => [ @{$hyp->actions}, $self ], + scope => $scope + ), + @events, + ); +} + +sub _build__update { + my ($self) = @_; + DX::Update::SetValue->new( + target_path => $self->target_path, + new_value => $self->new_value, + ); +} + +1; diff --git a/lib/DX/ActionBuilder/Normal.pm b/lib/DX/ActionBuilder/Normal.pm new file mode 100644 index 0000000..5aeca56 --- /dev/null +++ b/lib/DX/ActionBuilder/Normal.pm @@ -0,0 +1,17 @@ +package DX::ActionBuilder::Normal; + +use DX::Class; + +with 'DX::Role::ActionBuilder'; + +has target_path => (is => 'ro', required => 1); + +sub action_for_set_value { + my ($self, $value) = @_; + DX::Action::SetValue->new( + target_path => $self->target_path, + new_value => $value->but_set_action_builder($self) + ); +} + +1; diff --git a/lib/DX/ActionBuilder/UnsetValue.pm b/lib/DX/ActionBuilder/UnsetValue.pm new file mode 100644 index 0000000..6db2efd --- /dev/null +++ b/lib/DX/ActionBuilder/UnsetValue.pm @@ -0,0 +1,25 @@ +package DX::ActionBuilder::UnsetValue; + +use DX::Action::SetValue; +use DX::ActionBuilder::Normal; +use DX::Class; + +with 'DX::Role::ActionBuilder'; + +has target_path => (is => 'ro', required => 1); + +sub action_for_set_value { + my ($self, $value) = @_; + if (0) { # value_path / identity_path test + # bind value + } + my $ab = DX::ActionBuilder::Normal->new( + target_path => $self->target_path, + ); + DX::Action::SetValue->new( + target_path => $self->target_path, + new_value => $value->but_set_action_builder($ab), + ); +} + +1; diff --git a/lib/DX/DependencyMap.pm b/lib/DX/DependencyMap.pm new file mode 100644 index 0000000..1b55b93 --- /dev/null +++ b/lib/DX/DependencyMap.pm @@ -0,0 +1,135 @@ +package DX::DependencyMap; + +use DX::Utils qw(CONTENTS_OF INDICES_OF); +use Moo; + +# { x => [ { y => [ ... +# my $targ = $root; $targ = $targ->[0]{$_[0]} for @path +# my $deps = $targ->[$${$dep_type}]; + +has deps => (is => 'ro', required => 1); + +has revdeps => (is => 'ro', required => 1); + +sub new_empty { + my ($class) = @_; + $class->new(deps => {}, revdeps => {}); +} + +sub with_entry_for { + my ($self, $for_id, $deps_for) = @_; + my @expanded = $self->_expand_deps($deps_for); + my $new_revdeps = { + %{$self->revdeps}, + $for_id => \@expanded, + }; + my $new_deps = $self->_merge_deps_for($self->deps, $for_id, @expanded); + ref($self)->new( + deps => $new_deps, + revdeps => $new_revdeps + ); +} + +sub without_entries_for { + my ($self, @for_ids) = @_; + my %new_revdeps = %{$self->revdeps}; + my $new_deps = $self->deps; + $new_deps = $self->_unmerge_deps_for( + $new_deps, $_, @{$new_revdeps{$_}} + ) for @for_ids; + delete @new_revdeps{@for_ids}; + ref($self)->new( + deps => $new_deps, + revdeps => \%new_revdeps + ); +} + +sub _merge_deps_for { + my ($self, $deps, $for_id, @merge_these) = @_; + $self->_mangle_deps($deps, sub { + +{ %{$_[0]}, $for_id => 1 }; + }, @merge_these); +} + +sub _unmerge_deps_for { + my ($self, $deps, $for_id, @unmerge_these) = @_; + $self->_mangle_deps($deps, sub { + my %for_ids = %{$_[0]}; + delete $for_ids{$for_id}; + \%for_ids; + }, @unmerge_these); +} + +sub _mangle_deps { + my ($self, $deps, $mangler, @to_mangle) = @_; + my $root = [ $deps ]; + foreach my $mangle_this (@to_mangle) { + my ($type, @path) = @$mangle_this; + my $targ = $root; + foreach my $part (@path) { + my $sub = $targ->[0] = { %{$targ->[0]||{}} }; + $targ = $sub->{$part} = [ @{$sub->{$part}||[]} ]; + } + $targ->[$$$type] = $mangler->($targ->[$$$type]||{}); + } + return $root->[0]; +} + +sub _expand_deps { + my ($self, $deps) = @_; + my @exp; + DEP: foreach my $dep (@$deps) { + my ($type, @path) = @$dep; + push @exp, [ + $type, + map { ref() ? @{$_->identity_path or next DEP} : $_ } @path + ]; + } + return @exp; +} + +sub _dependents_of { + my ($self, $event) = @_; + my ($type, @path) = @$event; + my $root = [ $self->deps ]; + my $targ = $root; + my $last = pop @path; + my @dep_sets; + foreach my $part (@path) { + $targ = $targ->[0]{$part}; + # set/add/remove x.y or x.y.z affects contents of x + push @dep_sets, $targ->[$${+CONTENTS_OF}]; + } + if ($$$type) { + # add/remove x.y affects indices of x + push @dep_sets, $targ->[$${+INDICES_OF}]; + } + $targ = $targ->[0]{$last}; + { + # add/remove x affects existence of x + # set/add/remove x affects everything else too + push @dep_sets, @{$targ}[map $$$_, @$$type]; + } + # set/add/remove x affects all dependencies on x.y, x.y.z etc. + my @q = values %{$targ->[0]}; + while (my $el = shift @q) { + my ($el_kids, @el_deps) = @$el; + push @dep_sets, @el_deps; + push @q, values %{$el_kids||{}}; + } + return keys %{{ + map +($_ => 1), map keys %$_, grep defined, @dep_sets + }}; +} + +sub but_expire_dependents_of { + my ($self, @events) = @_; + my @expired = keys %{{ + map +($_ => 1), map $self->_dependents_of($_), @events + }}; + # Didn't expire anything? Don't clone self + return $self unless @expired; + ($self->without_entries_for(@expired), @expired); +} + +1; diff --git a/lib/DX/Hypothesis.pm b/lib/DX/Hypothesis.pm index 4c8c382..61993c5 100644 --- a/lib/DX/Hypothesis.pm +++ b/lib/DX/Hypothesis.pm @@ -10,11 +10,13 @@ has outstanding_propositions => (is => 'ro', required => 1); has actions => (is => 'ro', required => 1); +sub head_proposition { shift->outstanding_propositions->[0] } + sub with_actions { my ($self, @actions) = @_; my $hyp = $self; foreach my $act (@actions) { - ($hyp, my @events) = $act->dry_run_against($hyp); + ($hyp, my @events) = $act->dry_run($hyp); return undef unless $hyp; $hyp = $hyp->but_recheck_for(@events); return undef unless $hyp; @@ -36,12 +38,11 @@ sub resolve_head_dependent_on { my ($first, @rest) = @{$self->outstanding_propositions}; $self->but( resolved_propositions => $self->resolved_propositions - ->but_with_resolution( - proposition => $first, - depends_on => $depends, - at_depth => $self->scope->depth, + ->with_resolution_for( + $first, + $depends, ), - outstanding_propositons => \@rest, + outstanding_propositions => \@rest, ); } diff --git a/lib/DX/Predicate/IsDict.pm b/lib/DX/Predicate/IsDict.pm new file mode 100644 index 0000000..d30df47 --- /dev/null +++ b/lib/DX/Predicate/IsDict.pm @@ -0,0 +1,25 @@ +package DX::Predicate::IsDict; + +use DX::Utils qw(step dict TYPE_OF); +use DX::Class; + +with 'DX::Role::Predicate'; + +sub _possible_resolution_list { + my ($self, $arg) = @_; + if ($arg->is_set) { + die "is_dict called with non-dict" + unless $arg->isa('DX::Value::Dict'); + return step( + actions => [], + depends_on => [ [ TYPE_OF ,=> $arg ] ] + ); + } + my $set = $arg->action_for_set_value(dict()); + return step( + actions => [ $set ], + depends_on => [ [ TYPE_OF ,=> $arg ] ] + ); +} + +1; diff --git a/lib/DX/Predicate/MemberAt.pm b/lib/DX/Predicate/MemberAt.pm index d000e01..6dfc141 100644 --- a/lib/DX/Predicate/MemberAt.pm +++ b/lib/DX/Predicate/MemberAt.pm @@ -1,6 +1,6 @@ package DX::Predicate::MemberAt; -use DX::Utils qw(step INDICES INDEX_EXISTS); +use DX::Utils qw(step INDICES_OF EXISTENCE_OF); use DX::Class; with 'DX::Role::Predicate'; @@ -17,7 +17,7 @@ sub _possible_resolution_list { return ( ($key->is_set ? map $_->but_with_dependencies_on( - [ undef ,=> [ $coll, INDEX_EXISTS ,=> $key ], $key ] + [ undef ,=> [ $coll, EXISTENCE_OF ,=> $key ], $key ] ), do { if (my $cur_val = $coll->get_member_at($key)) { $self->_make_equal($cur_val, $value); @@ -42,7 +42,7 @@ sub _possible_resolution_list { my $set_key = $key->action_for_set_value($_); map $_->but_first($set_key) ->but_with_dependencies_on( - [ undef ,=> [ $coll, INDEX_EXISTS ,=> $key ] ] + [ undef ,=> [ $coll, EXISTENCE_OF ,=> $key ] ] ), $self->_make_equal($coll->get_member_at($_), $value); } $coll->index_list @@ -53,7 +53,7 @@ sub _possible_resolution_list { sub selection_depends_on { my ($self, $coll, $key, $value) = @_; - [ [ $coll => ($key->can_set_value ? INDICES : (INDEX_EXISTS ,=> $key)) ], + [ [ $coll => ($key->can_set_value ? INDICES_OF : (EXISTENCE_OF ,=> $key)) ], $key, $value, ] diff --git a/lib/DX/Proposition.pm b/lib/DX/Proposition.pm index 8e1e538..d67e2de 100644 --- a/lib/DX/Proposition.pm +++ b/lib/DX/Proposition.pm @@ -10,9 +10,9 @@ sub resolve_for { my ($self, $scope) = @_; my $predicate = $scope->lookup_predicate($self->predicate); my @args = map { ref($_) ? $_ : $scope->lookup($_) } @{$self->args}; - if (my $step = $scope->known_facts->resolution_step_for($predicate, @args)) { - return $step; - } + #if (my $step = $scope->known_facts->resolution_step_for($predicate, @args)) { + # return $step; + #} return $predicate->resolution_step_for(@args); } diff --git a/lib/DX/ResolvedPropositionSet.pm b/lib/DX/ResolvedPropositionSet.pm new file mode 100644 index 0000000..ff37b41 --- /dev/null +++ b/lib/DX/ResolvedPropositionSet.pm @@ -0,0 +1,57 @@ +package DX::ResolvedPropositionSet; + +use DX::DependencyMap; +use Moo; + +has dependency_map => (is => 'ro', required => 1); + +has propositions => (is => 'ro', required => 1); + +has scope_depth => (is => 'ro', required => 1); + +sub new_empty { + my ($class) = @_; + $class->new( + propositions => [], + dependency_map => DX::DependencyMap->new_empty, + scope_depth => 0, + ); +} + +sub with_resolution_for { + my ($self, $prop, $deps) = @_; + my $id = my @already = @{$self->propositions}; + my $new_depmap = $self->dependency_map + ->with_entry_for($id, $deps); + ref($self)->new( + dependency_map => $new_depmap, + propositions => [ @already, $prop ], + scope_depth => $self->scope_depth, + ); +} + +sub with_updated_dependencies_for { + my ($self, $prop, $deps) = @_; + my @props = @{$self->propositions}; + my ($id) = grep $props[$_] eq $prop, 0..$#props; + die "WHUT" unless $id; + my $new_depmap = $self->dependency_map + ->with_entry_for($id, $deps); + ref($self)->new( + dependency_map => $new_depmap, + propositions => \@props, + scope_depth => $self->scope_depth, + ); +} + +sub but_expire_for { + my ($self, @events) = @_; + my ($depmap, @expired_ids) = $self->dependency_map + ->but_expire_dependents_of(@events); + # Didn't expire anything? Don't clone self + return $self if $depmap eq $self->dependency_map; + die 'WHUT'; +} + + +1; diff --git a/lib/DX/Role/Update.pm b/lib/DX/Role/Update.pm index a3eace0..f7cfcb5 100644 --- a/lib/DX/Role/Update.pm +++ b/lib/DX/Role/Update.pm @@ -6,8 +6,8 @@ has target_path => (is => 'ro', required => 1); sub _with_value_at_path { my ($self, $scope, $final_value, @path) = @_; + return $final_value->($scope) unless @path; my ($first, @rest) = @path; - return $final_value->($scope) unless $first; my $inner = $scope->get_member_at($first); my $value = ( @rest diff --git a/lib/DX/Role/Value.pm b/lib/DX/Role/Value.pm index da75e82..9aae493 100644 --- a/lib/DX/Role/Value.pm +++ b/lib/DX/Role/Value.pm @@ -3,6 +3,8 @@ package DX::Role::Value; use DX::ActionBuilder::Null; use DX::Role; +has identity_path => (is => 'ro'); + has action_builder => ( is => 'ro', default => 'DX::ActionBuilder::Null', @@ -11,4 +13,9 @@ has action_builder => ( sub is_set { 1 } +sub but_set_action_builder { + my ($self, $ab) = @_; + $self->but(action_builder => $ab); +} + 1; diff --git a/lib/DX/Scope.pm b/lib/DX/Scope.pm index 979efb8..95fb3e3 100644 --- a/lib/DX/Scope.pm +++ b/lib/DX/Scope.pm @@ -8,7 +8,7 @@ has globals => (is => 'ro', required => 1); has locals => (is => 'ro', required => 1); -has known_facts => (is => 'ro', required => 1); +#has known_facts => (is => 'ro', required => 1); sub lookup_predicate { my ($self, $predicate) = @_; @@ -18,7 +18,8 @@ sub lookup_predicate { sub lookup { my ($self, $name) = @_; my $lookup_in = ($name =~ /^[_A-Z]/ ? $self->locals->[-1] : $self->globals); - return $lookup_in->get_member_at($name) || die "No such name in scope: $name"; + return $lookup_in->get_member_at($name) + or die "No such name in scope: $name"; } sub depth { $#{$_[0]->locals} } @@ -40,7 +41,7 @@ sub with_member_at { my ($self, $at, $value) = @_; if ($at =~ /^[0-9]+$/) { my @locals = @{$self->locals}; - $locals[$at] = $at; + $locals[$at] = $value; return $self->but( locals => \@locals ); diff --git a/lib/DX/SearchState.pm b/lib/DX/SearchState.pm index d97ab9b..6e9954f 100644 --- a/lib/DX/SearchState.pm +++ b/lib/DX/SearchState.pm @@ -18,7 +18,7 @@ sub with_one_step { STEP: while ($step) { my ($new_hyp, $alt_step) = $step->apply_to($hyp); if ($new_hyp) { - return ref($self)->new( + return $self->but( current_hypothesis => $new_hyp, ($alt_step ? (alternatives => [ diff --git a/lib/DX/Update/AddValue.pm b/lib/DX/Update/AddValue.pm index 81db27c..9d8ed9d 100644 --- a/lib/DX/Update/AddValue.pm +++ b/lib/DX/Update/AddValue.pm @@ -1,6 +1,6 @@ package DX::Update::AddValue; -use DX::Utils qw(INDICES INDEX_EXISTS); +use DX::Utils qw(VALUE_EXISTS); use DX::Class; with 'DX::Role::Update'; @@ -9,15 +9,14 @@ has new_value => (is => 'ro', required => 1); sub apply_to { my ($self, $scope) = @-; - my @path = @{$self->target_path}; + my @path = my @whole_path = @{$self->target_path}; my $target = pop @path; my $new_value = $self->new_value; return ( $self->_with_value_at_path( $scope, sub { $_[0]->with_member_at($target, $new_value) }, @path ), - [ @path, INDICES ], - [ @path, INDEX_EXISTS ,=> $target ], + [ VALUE_EXISTS ,=> @whole_path ], ); } diff --git a/lib/DX/Update/RemoveValue.pm b/lib/DX/Update/RemoveValue.pm index 9aff310..3bd0041 100644 --- a/lib/DX/Update/RemoveValue.pm +++ b/lib/DX/Update/RemoveValue.pm @@ -1,21 +1,19 @@ package DX::Update::RemoveValue; -use DX::Utils qw(INDICES INDEX_EXISTS); +use DX::Utils qw(VALUE_EXISTS); use DX::Class; with 'DX::Role::Update'; sub apply_to { my ($self, $scope) = @_; - my @path = @{$self->target_path}; + my @path = my @whole_path = @{$self->target_path}; my $target = pop @path; return ( $self->_with_value_at_path( $scope, sub { $_[0]->without_member_at($target) }, @path ), - [ @path, $target ], - [ @path, INDICES ], - [ @path, INDEX_EXISTS ,=> $target ] + [ VALUE_EXISTS ,=> @whole_path ], ); } diff --git a/lib/DX/Update/SetValue.pm b/lib/DX/Update/SetValue.pm index ff1e991..1cce847 100644 --- a/lib/DX/Update/SetValue.pm +++ b/lib/DX/Update/SetValue.pm @@ -1,5 +1,6 @@ package DX::Update::SetValue; +use DX::Utils qw(VALUE_SET); use DX::Class; with 'DX::Role::Update'; @@ -12,7 +13,7 @@ sub apply_to { my $new_value = $self->new_value; return ( $self->_with_value_at_path($scope, sub { $new_value }, @path), - \@path + [ VALUE_SET ,=> @path ], ); } diff --git a/lib/DX/Utils.pm b/lib/DX/Utils.pm index ce166e3..39b1794 100644 --- a/lib/DX/Utils.pm +++ b/lib/DX/Utils.pm @@ -3,11 +3,43 @@ package DX::Utils; use strictures 2; use Exporter 'import'; -our @EXPORT_OK = qw(INDICES INDEX_EXISTS ROOT_ONLY step string number); +my @const = ( + my @dep_types = qw(EXISTENCE_OF INDICES_OF TYPE_OF CONTENTS_OF), + my @ev_types = qw(VALUE_SET VALUE_EXISTS), +); -use constant INDICES => \*INDICES; -use constant INDEX_EXISTS => \*INDEX_EXISTS; -use constant ROOT_ONLY => \*ROOT_ONLY; +our @EXPORT_OK = ( + @const, + my @builders = qw(step string number dict proposition) +); + +our %EXPORT_TAGS = ( + all => \@EXPORT_OK, + dep_types => \@dep_types, + event_types => \@ev_types, + builders => \@builders, +); + +require constant; + +# use constant INDICES_OF => \*INDICES_OF; + +constant->import(+{ + map {; no strict 'refs'; $_ => \*$_ } @const +}); + +# $INDICES_OF = 1, ... + +do { no strict 'refs'; ${$dep_types[$_-1]} = $_ } for 1..@dep_types; + +# VALUE_EXISTS needs to trigger indices checks on its parent + +our $VALUE_EXISTS = 1; + +# VALUE_EXISTS triggers all types, VALUE_SET all but EXISTENCE_OF + +our @VALUE_EXISTS = (EXISTENCE_OF(), INDICES_OF(), TYPE_OF(), CONTENTS_OF()); +our @VALUE_SET = (INDICES_OF(), TYPE_OF(), CONTENTS_OF()); sub step { DX::Step::Normal->new(@_); @@ -21,10 +53,26 @@ sub number { DX::Value::Number->new(number_value => $_[0]); } +sub dict { + DX::Value::Dict->new( + members => { @_ }, + ); +} + +sub proposition { + my ($pred, @args) = @_; + DX::Proposition->new( + predicate => $pred, + args => \@args, + ); +} + # Here so that circular require doesn't stab us in the face require DX::Step::Normal; require DX::Value::String; require DX::Value::Number; +require DX::Value::Dict; +require DX::Proposition; 1; diff --git a/lib/DX/Value/Dict.pm b/lib/DX/Value/Dict.pm index 6d52aeb..0eca21c 100644 --- a/lib/DX/Value/Dict.pm +++ b/lib/DX/Value/Dict.pm @@ -16,13 +16,16 @@ has members => (is => 'ro', required => 1); sub get_member_at { my ($self, $at) = @_; - $self->members->{$at->string_value}; + $self->members->{ref($at) ? $at->string_value : $at}; } sub with_member_at { my ($self, $at, $value) = @_; $self->but( - members => { %{$self->members}, $at->string_value => $value } + members => { + %{$self->members}, + (ref($at) ? $at->string_value : $at) => $value + } ); } diff --git a/need b/need index e8fa218..1c358e5 100644 --- a/need +++ b/need @@ -1,15 +1,12 @@ -Action::SetValue - AddValue +Action::AddValue RemoveValue BindValue SetBoundValue AddBoundValue RemoveBoundValue -ActionBuilder::Simple - ::BoundValue - ::UnsetValue -KnownFactSet -ResolvedPropositionSet +ActionBuilder::BoundValue +? KnownFactSet +? KnownResolutionSet Predicate::Foreach Predicate::Findall Predicate::Forall diff --git a/t/01basics.t b/t/01basics.t new file mode 100644 index 0000000..d75d00b --- /dev/null +++ b/t/01basics.t @@ -0,0 +1,41 @@ +use strictures 1; +use Test::More; +use Module::Runtime qw(use_module); +use DX::Utils qw(:builders); + +my $ab = use_module('DX::ActionBuilder::UnsetValue')->new( + target_path => [ 0, 'X' ] +); + +my $scope = use_module('DX::Scope')->new( + predicates => { + '=' => use_module('DX::Predicate::Equals')->new, + member_at => use_module('DX::Predicate::MemberAt')->new, + is_dict => use_module('DX::Predicate::IsDict')->new, + }, + globals => dict(), + locals => [ + dict( + X => use_module('DX::Value::Unset')->new( + identity_path => [ 0, 'X' ], + action_builder => $ab, + ), + ), + ], +); + +my $hyp = use_module('DX::Hypothesis')->new( + scope => $scope, + resolved_propositions => use_module('DX::ResolvedPropositionSet')->new_empty, + outstanding_propositions => [ + proposition(is_dict => 'X'), + ], + actions => [], +); + +my $ss = use_module('DX::SearchState')->new( + current_hypothesis => $hyp, + alternatives => [], +); + +::Dwarn($ss->with_one_step); diff --git a/t/depmap.t b/t/depmap.t new file mode 100644 index 0000000..bc4b99c --- /dev/null +++ b/t/depmap.t @@ -0,0 +1,29 @@ +use strictures 2; +use DX::Utils qw(:all); +use DX::DependencyMap; +use List::Util qw(reduce); +use Test::More; + +my $start = DX::DependencyMap->new(deps => {}, revdeps => {}); + +{ + + my @xyz = qw(x x.y x.z); + + my $content = reduce { + $a->with_entry_for($b, [ [ CONTENTS_OF ,=> split(/\./,$b) ] ]) + } $start, @xyz; + + is( + (join ' ', sort $content->_dependents_of([ VALUE_SET ,=> qw(x y) ])), + 'x x.y', + 'x.y set clears contents_of x', + ); + is( + (join ' ', sort $content->_dependents_of([ VALUE_SET ,=> qw(x) ])), + 'x x.y x.z', + 'x set clears contents_of all x.*', + ); +} + +done_testing;