Skip to content

Commit

Permalink
Better support for negative arity
Browse files Browse the repository at this point in the history
Ruby 3 changed arity of procs made from symbols (because they are lambdas now). This revealed some logic issues (see what I did here). Thanks to this change I finally read the documentation on #arity and required improvements.

P.S. It seems that nobody uses arity < -1 for predicates
  • Loading branch information
flash-gordon committed Sep 26, 2020
1 parent e9222da commit cfd5f0d
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 34 deletions.
63 changes: 29 additions & 34 deletions lib/dry/logic/rule/interface.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module Dry
module Logic
class Rule
class Interface < ::Module
SPLAT = ["*rest"].freeze

attr_reader :arity

attr_reader :curried
Expand All @@ -18,12 +20,10 @@ def initialize(arity, curried)

define_constructor if curried?

if variable_arity?
define_splat_application
elsif constant?
if constant?
define_constant_application
else
define_fixed_application
define_application
end
end

Expand All @@ -32,7 +32,7 @@ def constant?
end

def variable_arity?
arity.equal?(-1)
arity.negative?
end

def curried?
Expand All @@ -41,7 +41,13 @@ def curried?

def unapplied
if variable_arity?
-1
unapplied = arity.abs - 1 - curried

if unapplied.negative?
0
else
unapplied
end
else
arity - curried
end
Expand All @@ -51,8 +57,19 @@ def name
if constant?
"Constant"
else
arity_str = variable_arity? ? "VariableArity" : "#{arity}Arity"
curried_str = curried? ? "#{curried}Curried" : EMPTY_STRING
arity_str =
if variable_arity?
"Variable#{arity.abs - 1}Arity"
else
"#{arity}Arity"
end

curried_str =
if curried?
"#{curried}Curried"
else
EMPTY_STRING
end

"#{arity_str}#{curried_str}"
end
Expand Down Expand Up @@ -91,32 +108,10 @@ def [](*)
end
end

def define_splat_application
application =
if curried?
"@predicate[#{curried_args.join(", ")}, *input]"
else
"@predicate[*input]"
end

module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
def call(*input)
if #{application}
Result::SUCCESS
else
Result.new(false, id) { ast(*input) }
end
end
def [](*input)
#{application}
end
RUBY
end

def define_fixed_application
parameters = unapplied_args.join(", ")
application = "@predicate[#{(curried_args + unapplied_args).join(", ")}]"
def define_application
splat = variable_arity? ? SPLAT : EMPTY_ARRAY
parameters = (unapplied_args + splat).join(", ")
application = "@predicate[#{(curried_args + unapplied_args + splat).join(", ")}]"

module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
def call(#{parameters})
Expand Down
27 changes: 27 additions & 0 deletions spec/unit/rule_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,33 @@ def num
end
end

describe "-2 arity" do
let(:options) { {args: [], arity: -2} }

it "accepts variable number of arguments" do
expect(rule.method(:call).arity).to be(-2)
expect(rule.method(:[]).arity).to be(-2)
end

context "curried 1" do
let(:options) { {args: [1], arity: -2} }

it "doesn't have required arguments" do
expect(rule.method(:call).arity).to be(-1)
expect(rule.method(:[]).arity).to be(-1)
end
end

context "curried 2" do
let(:options) { {args: [1, 2], arity: -2} }

it "doesn't have required arguments" do
expect(rule.method(:call).arity).to be(-1)
expect(rule.method(:[]).arity).to be(-1)
end
end
end

describe "constants" do
let(:options) { {args: [], arity: 0} }

Expand Down

0 comments on commit cfd5f0d

Please sign in to comment.