Module: Plumb
- Defined in:
- lib/plumb.rb,
lib/plumb/or.rb,
lib/plumb/and.rb,
lib/plumb/key.rb,
lib/plumb/not.rb,
lib/plumb/codec.rb,
lib/plumb/types.rb,
lib/plumb/union.rb,
lib/plumb/policy.rb,
lib/plumb/result.rb,
lib/plumb/encoder.rb,
lib/plumb/version.rb,
lib/plumb/deferred.rb,
lib/plumb/function.rb,
lib/plumb/hash_map.rb,
lib/plumb/metadata.rb,
lib/plumb/pipeline.rb,
lib/plumb/policies.rb,
lib/plumb/relation.rb,
lib/plumb/any_class.rb,
lib/plumb/decorator.rb,
lib/plumb/optimizer.rb,
lib/plumb/subtyping.rb,
lib/plumb/attributes.rb,
lib/plumb/composable.rb,
lib/plumb/constraint.rb,
lib/plumb/hash_class.rb,
lib/plumb/type_cache.rb,
lib/plumb/typed_step.rb,
lib/plumb/array_class.rb,
lib/plumb/conjunction.rb,
lib/plumb/disjunction.rb,
lib/plumb/never_class.rb,
lib/plumb/node_mapper.rb,
lib/plumb/range_class.rb,
lib/plumb/tagged_hash.rb,
lib/plumb/tuple_class.rb,
lib/plumb/value_class.rb,
lib/plumb/intersection.rb,
lib/plumb/static_class.rb,
lib/plumb/stream_class.rb,
lib/plumb/type_registry.rb,
lib/plumb/implementation.rb,
lib/plumb/interface_class.rb,
lib/plumb/mermaid_visitor.rb,
lib/plumb/covariant_fusion.rb,
lib/plumb/metadata_visitor.rb,
lib/plumb/semantic_matcher.rb,
lib/plumb/visitor_handlers.rb,
lib/plumb/json_schema_visitor.rb,
lib/plumb/attribute_value_match.rb
Defined Under Namespace
Modules: Attributes, Callable, Composable, Conjunction, CovariantFusion, Disjunction, Equality, Implementation, Naming, NodeMapper, Optimizer, SplitPolicy, Subtyping, TypeCache, TypeRegistry, TypedStep, Types, VisitorHandlers Classes: And, AnyClass, ArrayClass, AttributeValueMatch, Codec, Constraint, Decorator, Deferred, Encoder, FilteredHash, Function, GuaranteedFunction, HashClass, HashMap, InterfaceClass, Intersection, JSONSchemaVisitor, Key, MermaidVisitor, Metadata, MetadataVisitor, NeverClass, Not, Or, Pipeline, Policies, Policy, RangeClass, Result, StaticClass, StreamClass, TaggedHash, TupleClass, UndefinedClass, Union, ValueClass
Constant Summary collapse
- VERSION =
'0.2.0.beta.1'- ParseError =
Class.new(::TypeError)
- TypeError =
Raised by Composable#>> when chaining two steps whose types are provably incompatible (the left's #output_type is not a subtype of the right's #input_type).
Class.new(::TypeError)
- Undefined =
UndefinedClass.new.freeze
- BLANK_STRING =
''- BLANK_ARRAY =
[].freeze
- BLANK_HASH =
{}.freeze
- NOOP =
->(result) { result }
- COERCION_METHODS =
Ruby's explicit conversion methods and the type each produces. Lets
#transform(:to_i)expand to a typed transform to Integer (using:to_ias the callable), instead of spelling out#transform(Integer, &:to_i). { to_s: ::String, to_sym: ::Symbol, to_i: ::Integer, to_f: ::Float, to_r: ::Rational, to_c: ::Complex, to_a: ::Array, to_h: ::Hash, to_proc: ::Proc }.freeze
Class Method Summary collapse
- .decorate(type, &block) ⇒ Object
- .policies ⇒ Object
-
.policy(name, opts = {}) {|Step, Object, &block| ... } ⇒ Object
Register a policy with the given name and block.
- .resolve_base_types(node) ⇒ Object
-
.value_base_class(value) ⇒ Class
Recursively resolve the underlying Ruby classes ("base types") of a node.
Class Method Details
.decorate(type, &block) ⇒ Object
57 58 59 |
# File 'lib/plumb.rb', line 57 def self.decorate(type, &block) Decorator.call(type, &block) end |
.policies ⇒ Object
8 9 10 |
# File 'lib/plumb.rb', line 8 def self.policies @policies end |
.policy(name, opts = {}) {|Step, Object, &block| ... } ⇒ Object
Register a policy with the given name and block. Optionally define a method on the Composable method to call the policy. Example:
Plumb.policy(:multiply_by, for_type: Integer, helper: true) do |step, factor, &block|
step.transform(Integer) { |number| number * factor }
end
type = Types::Integer.multiply_by(2) type.parse(10) # => 20
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/plumb.rb', line 25 def self.policy(name, opts = {}, &block) name = name.to_sym if opts.is_a?(Hash) && block_given? for_type = opts[:for_type] || Object helper = opts[:helper] || false elsif opts.respond_to?(:call) && opts.respond_to?(:for_type) && opts.respond_to?(:helper) for_type = opts.for_type helper = opts.helper block = opts.method(:call) else raise ArgumentError, 'Expected a block or a hash with :for_type and :helper keys' end policies.register(for_type, name, block) return self unless helper if Composable.instance_methods.include?(name) raise Policies::MethodAlreadyDefinedError, "Method #{name} is already defined on Composable" end Composable.define_method(name) do |arg = Undefined, &bl| if arg == Undefined policy(name, &bl) else policy(name, arg, &bl) end end self end |
.resolve_base_types(node) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/plumb.rb', line 75 def self.resolve_base_types(node) return [node] if node.is_a?(::Class) return [] unless node.respond_to?(:node_name) # A transparent wrapper (Policy / Metadata / #as_node Node) only RE-LABELS the type # it wraps, so it has the same base types. Peeled here rather than per branch: a # Node's node_name is whatever #as_node was given, so it cannot be a `when` at all, # and every #as_node type would report NO base types — which callers read as # "unknown base, allow", silently skipping build-time checks like # `Types::Email[1..20]`. unwrapped = Plumb::Subtyping.unwrap_transparent(node) return resolve_base_types(unwrapped) unless unwrapped.equal?(node) case node.node_name when :or, :union node.children.flat_map { |child| resolve_base_types(child) } when :function resolve_base_types(node.output_type) when :intersection # A meet narrows a single value, so its base types are its LEFT's — # `String.where(size: 1..3)` is still a String. resolve_base_types(node.children[0]) when :and # Descend into whichever side carries the resulting type: a value-preserving right # NARROWS what the left produces, a converting right REPLACES it. # # Deliberately NOT `node.output_type`, which encodes the same rule but is not # guaranteed to be a smaller node — for `Array[<record with a coercing field>] # .where(size: 1..)` it is a DIFFERENT And whose own output type is itself, so # following it ping-pongs until the stack blows. A child always terminates. left, right = node.children resolve_base_types(Plumb::Subtyping.value_preserving?(right) ? left : right) when :constraint # A refinement matcher carries its base type — resolve that (eg. # `Integer[1..10]` => [Integer], `User.check {}` => the User's base types). return resolve_base_types(node.base) if node.base SemanticMatcher.matcher_domain(node.children.first) when :array, :tuple then [::Array] when :hash, :hash_map, :tagged_hash, :filtered_hash, :filtered_hash_map then [::Hash] when :stream then [::Enumerator] when :range then [::Range] when :not # A complement has no finite base-class representation. [] when :value # Literals match by ==, including across numeric classes. value = node.children.first value.equal?(Undefined) ? [] : [value_base_class(value)] when :static value = node.children.first [value.is_a?(::Class) ? value : value_base_class(value)] else node.respond_to?(:children) ? node.children.flat_map { |child| resolve_base_types(child) } : [] end end |
.value_base_class(value) ⇒ Class
Recursively resolve the underlying Ruby classes ("base types") of a node. Types::String => [String] Types::String | Integer => [String, Integer]
This is a temporary helper to preserve type-specific policy resolution
(see Composable#policy) until proper subtyping checks are implemented.
Returns the value's matching domain, widened to Numeric because numeric
equality crosses concrete classes (5 == 5.0).
Keeping a concrete numeric class would falsely make compatible literal and
numeric types appear disjoint and reduce their intersection to Never.
73 |
# File 'lib/plumb.rb', line 73 def self.value_base_class(value) = SemanticMatcher.value_domain(value) |