module Rize
Constants
- DC
- VERSION
Public Instance Methods
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
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
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 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
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
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
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
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
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
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
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 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 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
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
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
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
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
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 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
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