Defi prepares a method call, a name with its arguments, and sends it to any object, keeping what came back: the value returned, or the exception raised. The same call can be sent to as many objects as you like, to compare what each one does with it.
# Gemfile
gem "defi"Defi requires Ruby 3.3 or later, and depends on nothing.
A challenge is a method name, with positional arguments, keyword arguments and a block, as you would write them after a dot:
require "defi"
challenge = Defi::Challenge.new(:fetch, :b, 0)
challenge.to_s # => ".fetch(:b, 0)"Sent to an object with to, it brings back a value: what the method returned, or what it raised.
value = challenge.to({ b: 2 })
value.raised? # => false
value.object # => 2
value.call # => 2
value = Defi::Challenge.new(:fetch, :b).to({})
value.raised? # => true
value.object # => #<KeyError: key not found: :b>
value.call # raises the KeyError againKeyword arguments and the block go through as they are:
Defi::Challenge.new(:encode, "US-ASCII", undef: :replace).to("café").object # => "caf?"
Defi::Challenge.new(:map) { |n| n * 2 }.to([1, 2]).object # => [2, 4]The message is sent with public_send: a private method, or a BasicObject, which has no public_send, gives a NoMethodError. public_send protects the named method only: a challenge of send or instance_eval, which are public, reaches what they reach.
A challenge keeps as its value the exceptions a method raises on its own: a StandardError, a ScriptError (such as NotImplementedError), a SystemStackError, or the SystemExit of an exit or an abort. What is not the method's doing goes through: an Interrupt, a SignalException, a NoMemoryError, the exception a Timeout uses to end its block (a Timeout given a class to raise, such as RuntimeError, is kept like any StandardError), and any other subclass of Exception. Defi::Value::RAISED lists the classes kept. call raises the exception again, the same object, with its own cause.
to_s writes the call as .name(1, key: 2, &block): the arguments and the keys by their inspect, which runs their code and can change from one Ruby to the next (a Hash in 3.4, a Set in 4.0), the block as &block.
A challenge and a value are frozen. Their arguments are the objects given, not copies: a mutable argument stays shared with the caller.
Compared with 3.0.1:
Defi::MethodbecomesDefi::Challenge, a plain class, no longer aBasicObject, indefi/challenge.rb:require "defi/method"becomesrequire "defi".Kernel#Defi, which every object received as a public method, is gone: writeDefi::Challenge.new.tokeeps the exceptions of the method only, where 3.0.1 caught everyException,TimeoutandInterruptincluded. A name that is not a Symbol raisesArgumentError, as documented, with a new message; theBasicObjectcould not evenraise.callkeeps the cause of the exception it raises again.Challenge#to_swrites keyword arguments askey: valueand the block as&block.argsandoptsare frozen.Value.newtakes the object and whether it was raised;Value::RAISEandValue::RETURNare gone, as areto_hon both classes,to_son a value and the custominspectmethods.- Ruby 3.3 or later; tested on 3.3, 3.4, 4.0 and head.
Defi follows Semantic Versioning 2.0.
The gem is available as open source under the terms of the MIT License.
This project is sponsored by Sashité