Skip to content

Allow evaluation of variables, procs, and lambdas with the same level of flexibility.

License

Notifications You must be signed in to change notification settings

reeganviljoen/proc_eval

Repository files navigation

ProcEval

This ruby gem adds an evaulate method to Proc and Object instances through the use of Refinements.

The goal of this gem is to allow evaluation of variables, procs, and lambdas with the same level of flexibility.

Example of the differences between procs and lambdas:

proc = proc {|a, b, c| [a, b, c] }
proc.call() # [nil,  nil, nil]

lambda = ->(a, b, c) { [a, b, c] }
lambda.call() # ArgumentError: wrong number of arguments (given 0, expected 3)

The evaluate method has been added to the Object class to simply return the value of the variable. The evaluate method is overriden on the Proc class to allow parameters to be passed to lambdas in the same flexible way as procs. This takes into consideration, required/optional/remaining parameters, and required/optional/remaining keyword parameters.

Usecase Examples

My gem or library or dsl requires a value from the developer. I want to allow the developer the greatest possible flexibility with the value they provide.

A contrived usecase of allowing a develop to define an OpenIdConnect endpoint at config time vs request time:

# define endpoint at config time
OpenIdConnectClient.config do |c|
  c.authorize_endpoint 'https://iknowthis.com/at/config/time'
end

# define endpoint at request time
OpenIdConnectClient.config do |c|
  c.authorize_endpoint ->(request) { OpenIdConnectProvider.find_by(name: request.params['provider_name']) }
end

By using the evaluate method within my gem/library/dsl, I do not need to be concerned with whether the developer provides a value, proc, or lambda. Likewise, the developer has greater flexibility when using my gem/library/dsl as they have greater options for returning the required value.

Compatibility

Because this gem makes use of keyword parameters and refinements, it is only compatible with Ruby version 2.0.0 and above.

Usage

In your Gemfile add gem 'proc_eval'.

In your codebase add require 'proc_eval'.

The refinement methods in the gem can be used by including using ProcEval in the file, class definition, or module definition in which you wish to use the refinement.

Class and Module usage

The refinement methods can be activated for use within a specific Class or Module.

class ProcEvalClassExamples
  using ProcEval

  def example1
    a = ->(a) { a }
    a.evaluate('hello', 'world')
  end

  def example2
    a = ->(a, b, c, d, e, f) { [a, b, c, d, e, f] }
    a.evaluate(1, 2, 3, 4)
  end

  def example3
    a = ->(a) { a }
    a.evaluate('Im a proc!!!', 'world')
  end

  def example4
    a = 'im a value!!!'
    a.evaluate('hello', 'world')
  end
end

e = ProcEvalClassExamples.new
e.example1 # "hello"
e.example2 # [1, 2, 3, 4, nil, nil, nil]
e.example3 # "Im a proc!!!"
e.example4 # "im a value!!!"

module ProcEvalModuleExamples
  using ProcEval
  extend self

  def example1
    a = ->(a) { a }
    a.evaluate('hello', 'world')
  end

  def example2
    a = ->(a, b, c, d, e, f) { [a, b, c, d, e, f] }
    a.evaluate(1, 2, 3, 4)
  end

  def example3
    a = ->(a) { a }
    a.evaluate('Im a proc!!!', 'world')
  end

  def example4
    a = 'im a value!!!'
    a.evaluate('hello', 'world')
  end
end

ProcEvalModuleExamples.example1 # "hello"
ProcEvalModuleExamples.example2 # [1, 2, 3, 4, nil, nil, nil]
ProcEvalModuleExamples.example3 # "Im a proc!!!"
ProcEvalModuleExamples.example4 # "im a value!!!"

Another example showing a different pattern of usage:

class Example
  using ProcEval

  def initialize(value)
    @value = value
  end

  def evaluate_value(*args, **options)
    @value.evaluate(*args, **options)
  end
end

# Example 1: Evaluating a plain value
e1 = Example.new('Hello')
e1.evaluate_value # "Hello"
e1.evaluate_value('World')  # "Hello"
e1.evaluate_value(hello: 'World') # "Hello"

# Example 2: Evaluating a lambda
lambda = ->(req, opt = nil, *rest, keyreq:, keyopt: nil, **options) {
  [req, opt, rest, keyreq, keyopt, options]
}

e2 = Example.new(lambda)
begin
  e2.evaluate_value # ArgumentError: missing keywords: keyreq
rescue => e
  puts e.message
end

e2.evaluate_value(1, keyreq: true) # [1, nil, [], true, nil, {}]
e2.evaluate_value(1, keyreq: true) # [1, nil, [], true, nil, {}]
e2.evaluate_value(1, :optional, keyreq: true) # [1, :optional, [], true, nil, {}]
e2.evaluate_value(1, keyreq: true, keyopt: :optional) # [1, nil, [], true, :optional, {}]
e2.evaluate_value(1, :optional, 'another', 2, keyreq: true, keyopt: :optional, my_key: 'Hello World') # [1, :optional, ["another", 2], true, :optional, {:my_key=>"Hello World"}]

Top Level usage

Please note that the below example will not work if copied and pasted into an irb or pry console. The using statement can only be used at the top level, or within a class or module definition. To test the example, place the code into a file and run with the command ruby example.rb.

# example.rb

using ProcEval # activate the refinements for the current file

proc = proc {|a, b, c| [a, b, c] }
proc.evaluate() # [nil,  nil, nil]

lambda = ->(a, b, c) { [a, b, c] }
lambda.evaluate() # [nil,  nil, nil]

var1 = 1
var1.evaluate('hello', 'world') # 1

var2 = ->(a) { a }
var2.evaluate('hello', 'world') # "hello"

Further Reading

For information on Refinements, see:

About

Allow evaluation of variables, procs, and lambdas with the same level of flexibility.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages