module Rize

Constants

DC
VERSION

Public Instance Methods

at_least(func, allowed_call_count) click to toggle source

Raises an error until after a function is called a certain number of times, following which the function is executed. Raises instead of returning nil to provide better transparency to callers.

@param func [Proc, Lambda, or Method] A proc, lambda or method. @param allowed_call_count [Fixnum] The minimum number of times this function needs to be called to start executing. @raise TooFewCallsError [StandardError] Exception raised when the function hasn’t been called enough times.

@return [Lambda] A lambda that places the appropriate call restrictions on func.

@example Execute a function only on the 3rd attempt.

succeed = lambda { |*args| "success!" }
persevere = Rize.at_least(succeed, 3)
3.times do
  begin
    persevere.call
  rescue Rize::TooFewCallsError
    puts "keep trying"
  end
end
"keep trying"
"keep trying"
"success!"
# File lib/rize/functional.rb, line 92
def at_least(func, allowed_call_count)
  call_count = 0
  lambda do |*args|
    call_count += 1
    raise TooFewCallsError, "Minimum call count is #{allowed_call_count}." if call_count < allowed_call_count
    func.call(*args)
  end
end
at_most(func, allowed_call_count) click to toggle source

Executes a function upto a certain number of times, following which an error is raised. Raises instead of returning nil to provide better transparency to callers.

@param func [Proc, Lambda, or Method] A proc, lambda or method. @param allowed_call_count [Fixnum] The maximum number of times the function can execute. @raise TooManyCallsError [StandardError] Exception raised when the function has been called too many times.

@return [Lambda] A lambda that places the appropriate call restrictions on func.

@example Execute a function 2 times, then fail on attempt 3 onwards.

are_we_there_yet = lambda { |*args| "Are we there yet?" }
but_are_we_really_there_yet = Rize.at_most(are_we_there_yet, 2)
2.times do
  begin
    but_are_we_really_there_yet.call
  rescue Rize::TooManyCallsError
    puts "That's it, I'm turning this car around"
  end
end
"Are we there yet?"
"Are we there yet?"
"That's it, I'm turning this car around"

@example Try connecting to a database 3 times. Give up on attempt 4.

MAX_RETRIES = 3
try_connect = Rize.at_most( lambda { |db| db.connect }, MAX_RETRIES )
loop do
  begin
    try_connect.call(db)
  rescue TooManyCallsError
    # If we've tried too many times, give up
    break
  rescue ConnectionError
    # If we can't connect, try again
    try_connect.call(db)
  end
end
# File lib/rize/functional.rb, line 138
def at_most(func, allowed_call_count)
  call_count = 0
  lambda do |*args|
    call_count += 1
    raise TooManyCallsError, "Maximum call count is #{allowed_call_count}." if call_count > allowed_call_count
    func.call(*args)
  end
end
call_all(funcs, *args) click to toggle source

Internal helper used by compose to actually call functions.

@param funcs [Array] An array of procs, lambdas or methods. @param *args [Object] A variable-length number of arguments to call the actual functions with.

# File lib/rize/functional.rb, line 221
def call_all(funcs, *args)
  return funcs[0].call(*args) if funcs.length == 1

  funcs[0].call(call_all(funcs.drop(1), *args))
end
compose(*funcs) click to toggle source

Compose multiple procs, lambdas or methods.

@param *funcs [Proc, Lambda, Method] A variable-length number of procs, lambdas or methods to compose.

@return [Lambda] A lambda that is the composition of the inputs. compose(f, g, h).call(arg) is the same as f(g(h.call(arg))) @example Compose various mathematical operations together to compute (2(a + b))^2.

f = lambda { |x| x**2 }
g = lambda { |x| 2 * x }
h = lambda { |x, y| x + y }
composed = Rize.compose(f, g, h)
composed.call(2, 3)
100
# File lib/rize/functional.rb, line 50
def compose(*funcs)
  -> (*args) { call_all(funcs, *args) }
end
each_n(*args) { |*elems| ... } click to toggle source

Iterate over multiple arrays together.

The same as doing [block(a1, b1, c1), block(a2, b2, c2)] for arrays [a1, b1, c1] and [a2, b2, c2].

Raises an ArgumentError if arrays are of unequal length.

@param args [Array] A variable-length number of arrays. @yield [*args] A block that acts upon elements at a particular index in the array.

@return [Array] The input arrays. @example Print the transposed version of an array of arrays.

Rize.each_n([1, 2, 3], [4, 5, 6], [7, 8, 9]) { |a, b, c| puts "#{a} #{b} #{c}" }
1 4 7
2 5 8
3 6 9
# File lib/rize/iteration.rb, line 136
def each_n(*args)
  expected_length = args[0].length
  if args.any? { |arr| arr.length != expected_length }
    raise ArgumentError, "Expected all inputs to be of length #{expected_length}"
  end
  hd(args).zip(*tl(args)).each do |elems|
    yield(*elems)
  end
end
flatter_map(arr, &block) click to toggle source

Variation on Enumerable#flat_map that flattens the array before running the block. Useful when dealing with an array of arrays, where we really want to operate on the underlying elements.

@param arr [Array] The array to be operated on.

@yield [elem] The block to be called.

@return [Array] The result of calling flat_map on the flattened array.

@example Capitalize each letter in an array of arrays.

Rize.flatter_map([["a", "b"], [["c"], ["d"]]], &:capitalize)
["A", "B", "C", "D"]

@example Capitalize each letter in an array of arrays, and also return the non-capitalized version.

Rize.flatter_map([["a", "b"], [["c"], ["d"]]]) { |el| [el, el.capitalize]  }
["a", "A", "b", "B", "c", "C", "d", "D"]
# File lib/rize/iteration.rb, line 215
def flatter_map(arr, &block)
  arr.flatten.flat_map(&block)
end
frequencies(arr) { |el| ... } click to toggle source

Find how many times a block evaluates to a particular result in an array.

@param arr [Array] The array over which we’re counting frequencies. @yield [elem] A block whose results we use to calculate frequencies.

@return [Hash] A hash containing the count of each of block’s output values from the array. The keys are the various outputs, and the values are the number of times said outputs occurred. @example Count the elements in an array.

Rize.frequencies([1, 2, 3, 1]) { |el| el }
{ 1 => 2, 2 => 1, 3 => 1 }

@example Count the even numbers in an array.

Rize.frequencies([1, 2, 3, 1]) { |el| el.even? }
{ true => 1, false => 3 }
# File lib/rize/iteration.rb, line 86
def frequencies(arr)
  hvalmap(arr.group_by { |el| yield(el) }, &:length)
end
hd(arr) click to toggle source

Returns the first element of an array, or nil if the array is empty.

@param arr [Array] The array from which we want the head.

@return [Object] The first element of the array. @example Get the first element of an array.

Rize.hd [1, 2, 3]
1

@example

Rize.hd []
nil
# File lib/rize/iteration.rb, line 54
def hd(arr)
  arr[0]
end
hkeymap(hsh) click to toggle source

Map over the keys of a hash.

@param hsh [Hash] The hash to be mapped over. @yield [key] A block that acts upon the hash keys.

@return [Hash] Returns a new hash with updated keys, and unchanged values. @example Map over a hash’s keys.

Rize.hkeymap({a: 1, b: 2}, &:to_s)
{ "a" => 1, "b" => 2 }
# File lib/rize/iteration.rb, line 26
def hkeymap(hsh)
  Hash[hsh.map { |k, v| [yield(k), v] }]
end
hmap(hsh) click to toggle source

Map over the keys and values of a hash.

@param hsh [Hash] The hash to be mapped over. @yield [key, value] A block which returns in the form [key, value].

@return [Hash] Returns a new hash with the results of running the block over it. @example Map over a hash

Rize.hmap({a: 1, b: 2}) { |k,v| [k.to_s, v + 1] }
{ "a" => 2, "b" => 3 }
# File lib/rize/iteration.rb, line 13
def hmap(hsh)
  Hash[hsh.map { |k, v| yield(k, v) }]
end
hvalmap(hsh) click to toggle source

Map over the values of a hash.

@param hsh [Hash] The hash to be mapped over. @yield [value] A block that acts upon the hash values

@return [Hash] Returns a new hash with updated values, and unchanged keys. @example Map over a hash’s values.

Rize.hvalmap({a: 1, b: 2}, &:to_s)
{ a: "1", b: "2" }
# File lib/rize/iteration.rb, line 39
def hvalmap(hsh)
  Hash[hsh.map { |k, v| [k, yield(v)] }]
end
lazy_repeat() { || ... } click to toggle source

Lazy version of repeat. Repeat a block N times, and return a lazy enumerator which can be forced for results.

@yield The block to be called.

@return [Enumerator::Lazy] A lazy enumerator that can be evaluated for the desired number of results. @example Mass-assign several variables to different random numbers.

a, b, c = Rize.repeat { Random.new.rand(50) }.first(3)
a
24
b
10
c
18

@example Initialize multiple FactoryGirl objects in one go.

u1, u2, u3 = Rize.repeat { FactoryGirl.create(:user) }.first(3)
u1
<User Object 1>
u2
<User Object 2>
u3
<User Object 3>
# File lib/rize/iteration.rb, line 196
def lazy_repeat
  (1..Float::INFINITY).lazy.map { yield }
end
map_n(*args) { |*elems| ... } click to toggle source

Map over multiple arrays together.

The same as doing [block(a1, b1, c1), block(a2, b2, c2)] for arrays [a1, b1, c1] and [a2, b2, c2].

Raises an ArgumentError if arrays are of unequal length.

@param args [Array] A variable-length number of arrays. @yield [*args] A block that acts upon elements at a particular index in the array.

@return [Array] The result of calling the block over the matching array elements. @example Sum all the elements at the same position across multiple arrays.

Rize.map_n([1, 2, 3], [4, 5, 6], [7, 8, 9]) { |*args| args.reduce(:+) }
[12, 15, 18]

@example Subtract the second array’s element from the first, and multiply by the third.

Rize.map_n([1, 2, 3], [4, 5, 6], [7, 8, 9]) { |a, b, c| (a - b) * c }
[-21, -24, -27]

@example Try with arrays of unequal length.

Rize.map_n([1, 2], [1, 2, 3]) { |*args| args.reduce(:+) }
ArgumentError: Expected all inputs to be of length 2
# File lib/rize/iteration.rb, line 110
def map_n(*args)
  expected_length = args[0].length
  if args.any? { |arr| arr.length != expected_length }
    raise ArgumentError, "Expected all inputs to be of length #{expected_length}"
  end
  hd(args).zip(*tl(args)).map do |elems|
    yield(*elems)
  end
end
memoize(func) click to toggle source

Returns a memoized version of a given proc, lambda, or method.

@param func [Proc, Lambda, Method] The proc, lambda, or method to memoize.

@return [Lambda] A lambda that is the memoized version of the input function. @example Memoize an expensive function.

expensive_lambda = lambda do |arg|
  puts "very expensive computation"
  arg
end
memoized = Rize.memoize(expensive_lambda)

memoized.call(1)
"very expensive computation"
1
memoized.call(1)
1
memoized.call(2)
"very expensive computation"
2
memoized.call(2)
2
# File lib/rize/functional.rb, line 26
def memoize(func)
  memo = {}
  call_count = Hash.new(0)
  lambda do |*args|
    return memo[args] if call_count[args] == 1
    memo[args] = func.call(*args)
    call_count[args] += 1
    memo[args]
  end
end
merge_keyword(prefilled_kwargs, new_kwargs) click to toggle source

Internal method used by partial to handle keyword arguments. Returns [] if passed in empty hashes. @param prefilled_kwargs [Hash] Prefilled kwargs supplied to Rize.partial. @param new_kwargs [Hash] kwargs supplied at call-time of the function.

@return [Array] An array holding the merged keyword arguments.

# File lib/rize/functional.rb, line 212
def merge_keyword(prefilled_kwargs, new_kwargs)
  return [] if prefilled_kwargs.empty? && new_kwargs.empty?
  [prefilled_kwargs.merge(new_kwargs)]
end
merge_positional(prefilled_args, new_args) click to toggle source

Internal method used by partial to handle positional arguments. Given arrays [1, Rize::DC, 3] and [2], returns [1, 2, 3]. @param prefilled_args [Array] Prefilled args supplied to Rize.partial. @param new_args [Array] Args supplied at call-time of the function.

@return [Array] the merged arguments in the manner described above.

# File lib/rize/functional.rb, line 195
def merge_positional(prefilled_args, new_args)
  tmp_new_args = new_args.dup
  prefilled_args.map do |elem|
    if elem == DC
      tmp_new_args.shift
    else
      elem
    end
  end + tmp_new_args
end
negate(func) click to toggle source

Returns a negated version of a proc, lambda, or method. The input function should return a boolean. @param func [Proc, Lambda, Method] A proc, lambda, or method to negate.

@return [Lambda] A lambda that is the negation of func.

@example Given a function that checks evenness, create a function that checks oddness.

even = lambda { |x| x.even? }
odd = Rize.negate(even)
# File lib/rize/functional.rb, line 63
def negate(func)
  -> (*args) { !func.call(*args) }
end
partial(func, *args, **kwargs) click to toggle source

Partially supply the arguments to a proc, lambda, or method.

@param func [Proc, Lambda, Method] The proc, lambda, or method to partially supply arguments to. @param *args [Object] A variable-length number of positional arguments. Use Rize::DC as a ‘don’t care’ variable to signify that we’d like to supply this argument later. This is useful, for example, if we have arguments 1 and 3, but are waiting on argument 2. @param **kwargs [Object] A variable-length number of keyword arguments.

@return [Lambda] A lambda that is the partially filled version of the input function. @example Supply the second and third positional arguments, but not the first.

final_lambda = lambda do |a, b, c|
  (a - b) * c
end
# Supply b and c.
partial_lambda = Rize.partial(final_lambda, Rize::DC, 2, 3)
# Supply a.
partial_lambda.call(1)
-3  # (1 - 2) * 3

@example Partial with keyword arguments.

final_lambda = lambda do |a:, b:, c:|
  (a - b) * c
end
Supply a: and c:.
partial_lambda = Rize.partial(final_lambda, a:1, c: 3)
Supply b:.
partial_lambda.call(b: 2)
-3 # (1 - 2) * 3

@example Partial with positional and keyword arguments.

final_lambda = lambda do |a, b, c:|
  (a - b) * c
end
Supply a and c:.
partial_lambda = Rize.partial(final_lambda, 1, c: 3)
Supply b.
partial_lambda.call(2)
-3 # (1 - 2) * 3
# File lib/rize/functional.rb, line 183
def partial(func, *args, **kwargs)
  lambda do |*new_args, **new_kwargs|
    func.call(*(merge_positional(args, new_args) + merge_keyword(kwargs, new_kwargs)))
  end
end
repeat(count) { || ... } click to toggle source

Repeat a block N times, and return an array of the results.

@param count [Fixnum] The number of times to repeat a block. @yield The block to be called.

@return [Array] The result of running block, ‘count` times. @example Mass-assign several variables to different random numbers.

a, b, c = Rize.repeat { Random.new.rand(50) }
a
24
b
10
c
18

@example Initialize multiple FactoryGirl objects in one go.

u1, u2, u3 = Rize.repeat { FactoryGirl.create(:user) }
u1
<User Object 1>
u2
<User Object 2>
u3
<User Object 3>
# File lib/rize/iteration.rb, line 168
def repeat(count)
  result = []
  count.times { result << yield }
  result
end
tl(arr) click to toggle source

Returns all but the first element of the array.

@param arr [Array] The array from which we want the tail.

@return [Array] An array containing all but the first element of the input. @example Get all but the first element of the array.

Rize.tl [1, 2, 3]
[2, 3]

@example

Rize.tl []
[]
# File lib/rize/iteration.rb, line 69
def tl(arr)
  arr.drop(1)
end