1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 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
|
# frozen_string_literal: true
require "concurrent/map"
require "dry/core/constants"
require "dry/core/equalizer"
require "dry/logic/operations"
require "dry/logic/result"
require "dry/logic/rule/interface"
module Dry
module Logic
def self.Rule(*args, **options, &block)
if args.any?
Rule.build(*args, **options)
elsif block
Rule.build(block, **options)
end
end
class Rule
include Core::Constants
include Dry::Equalizer(:predicate, :options)
include Operators
attr_reader :predicate
attr_reader :options
attr_reader :args
attr_reader :arity
def self.interfaces
@interfaces ||= ::Concurrent::Map.new
end
def self.specialize(arity, curried, base = Rule)
base.interfaces.fetch_or_store([arity, curried]) do
interface = Interface.new(arity, curried)
klass = Class.new(base) { include interface }
base.const_set("#{base.name.split("::").last}#{interface.name}", klass)
klass
end
end
def self.build(predicate, args: EMPTY_ARRAY, arity: predicate.arity, **options)
specialize(arity, args.size).new(predicate, {args: args, arity: arity, **options})
end
def initialize(predicate, options = EMPTY_HASH)
@predicate = predicate
@options = options
@args = options[:args] || EMPTY_ARRAY
@arity = options[:arity] || predicate.arity
end
def type
:rule
end
def id
options[:id]
end
def curry(*new_args)
with(args: args + new_args)
end
def bind(object)
if predicate.respond_to?(:bind)
self.class.build(predicate.bind(object), **options)
else
self.class.build(
-> *args { object.instance_exec(*args, &predicate) },
**options, arity: arity, parameters: parameters
)
end
end
def eval_args(object)
with(args: args.map { |arg| UnboundMethod === arg ? arg.bind(object).() : arg })
end
def with(new_opts)
self.class.build(predicate, **options, **new_opts)
end
def parameters
options[:parameters] || predicate.parameters
end
def ast(input = Undefined)
[:predicate, [id, args_with_names(input)]]
end
private
def args_with_names(*input)
parameters.map(&:last).zip(args + input)
end
end
end
end
|