Class: Plumb::And
- Inherits:
-
Object
- Object
- Plumb::And
- Includes:
- Composable, Conjunction
- Defined in:
- lib/plumb/and.rb
Overview
SEQUENTIAL COMPOSITION — a morphism left.source -> right.target, built by
Composable#>> when some side changes the value. The pure-refinement case
(neither side converts) is Intersection instead; see
Conjunction for why the two must be different nodes.
#input_type / #output_type are what the chain as a whole CONSUMES and PRODUCES,
not its two sides (those are children): (String -> Integer) >> (Integer -> Integer) consumes String and produces Integer. One hop at construction suffices,
since the children are already resolved by the same rule — O(1) per node, and off
the memoization path entirely.
Instance Attribute Summary
Attributes included from Conjunction
#children, #input_type, #output_type
Instance Method Summary collapse
- #absorb_input(type) ⇒ Object
-
#absorb_output(type) ⇒ Object
Boundary absorption re-associates for the same reason #fuse_with does: the boundary a chain presents to a neighbour belongs to the step at that END of it, so
And(gate, f) >> Types::Floatreachesf's output slot, andTypes::Integer >> And(f, x)reachesf's input slot. -
#fuse_with(other) ⇒ Object
Fuse
self >> otherby RE-ASSOCIATING:>>is associative, so when the tail can absorbotherwe rebuild around the fused tail. -
#initialize(left, right) ⇒ And
constructor
A new instance of And.
-
#subtype_identity ⇒ Object
Identified for subtyping by what it PRODUCES, like Function and Implementation: the input constraints do not carry through.
Methods included from Conjunction
Methods included from Composable
#&, #/, #>>, #[], #accepted_type, #as_node, #build, #check, #children, #defer, #fusable_step?, #generate, #idempotent?, included, #input_type, #invalid, #invoke, #match, #metadata, #not, #output_type, #pipeline, #policy, resolve_operand, #static, #to_json_schema, #to_mermaid, #to_plumb_type, #to_s, #transform, #value, #value_preserving?, #where, #with, wrap, #|
Methods included from Callable
Constructor Details
#initialize(left, right) ⇒ And
Returns a new instance of And.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/plumb/and.rb', line 21 def initialize(left, right) @left = left @right = right @input_type = left.input_type # A value-preserving right NARROWS what the left produces rather than replacing # it, so the chain produces the MEET of the two: `(String -> Integer)` then # `where(size: 10)` produces an Integer of size 10, not the bare `(size === 10)`. # A converting right replaces the value, so its own output stands. # # Conjunction.build, not Intersection.new — `left.output_type` need not preserve # values (a record drops undeclared keys, a Static replaces it), and only the # classifier may decide. The `lo.equal?(left)` guard is the fixpoint: a left that # IS its own output type would otherwise recurse forever. @output_type = if Plumb::Subtyping.value_preserving?(right) lo = left.output_type lo.equal?(left) ? self : Conjunction.build(lo, right) else right.output_type end @children = [left, right].freeze freeze end |
Instance Method Details
#absorb_input(type) ⇒ Object
78 79 80 81 |
# File 'lib/plumb/and.rb', line 78 def absorb_input(type) absorbed = @left.absorb_input(type) absorbed && Conjunction.build(absorbed, @right) end |
#absorb_output(type) ⇒ Object
Boundary absorption re-associates for the same reason #fuse_with does: the
boundary a chain presents to a neighbour belongs to the step at that END of it,
so And(gate, f) >> Types::Float reaches f's output slot, and `Types::Integer
And(f, x)
reachesf`'s input slot. Nil when that end declines, and the rebuilt half carries its own soundness proof. @see Composable#absorb_output
73 74 75 76 |
# File 'lib/plumb/and.rb', line 73 def absorb_output(type) absorbed = @right.absorb_output(type) absorbed && Conjunction.build(@left, absorbed) end |
#fuse_with(other) ⇒ Object
Fuse self >> other by RE-ASSOCIATING: >> is associative, so when the tail can
absorb other we rebuild around the fused tail. Without this, one non-fusable
step at the head blocks every later step from reducing — a pipeline behind a type
gate is And(gate, step1), and only Function-to-Function fuses, so step2
onwards could never join.
The soundness proof stays with the node that owns it: this only re-associates,
and @right.fuse_with(other) carries its own boundary check. Nil when the tail
declines. Recursion is bounded by the chain's depth.
63 64 65 66 |
# File 'lib/plumb/and.rb', line 63 def fuse_with(other) fused = @right.fuse_with(other) fused && Conjunction.build(@left, fused) end |
#subtype_identity ⇒ Object
Identified for subtyping by what it PRODUCES, like Function and Implementation: the input constraints do not carry through. This is what stops the meet rule reaching a chain that converts. @see Composable#subtype_identity
47 |
# File 'lib/plumb/and.rb', line 47 def subtype_identity = @output_type |