Class: Proc

Inherits:
Object show all
Includes:
MethodAndProcExtensions
Defined in:
lib/abstractivator/proc_ext.rb

Defined Under Namespace

Classes: LooseCallInfo

Constant Summary

Constants included from MethodAndProcExtensions

MethodAndProcExtensions::KEYWORD_PARAMETER_TYPES

Class Method Summary collapse

Instance Method Summary collapse

Methods included from MethodAndProcExtensions

#accepts_keywords, #loosen_args

Class Method Details

.compose(*procs) ⇒ Object

composes procedures. compose(f, g, h) returns the procedure proc { |x| f.call(g.call(h.call(x))) }



31
32
33
# File 'lib/abstractivator/proc_ext.rb', line 31

def self.compose(*procs)
  procs.map(&:to_proc).inject_right(identity) { |inner, p| p.compose(inner) }
end

.identityObject

returns the identity function



50
51
52
# File 'lib/abstractivator/proc_ext.rb', line 50

def self.identity
  proc {|x| x}
end

.loose_call(x, args, kws = {}, &block) ⇒ Object

Tries to coerce x into a procedure, then calls it with the given argument list. If x cannot be coerced into a procedure, returns x. This method is optimized for use cases typically found in tight loops, namely where x is either a symbol or a keyword-less fixed-arity proc. It attempts to minimize the number of intermediate arrays created for these cases (as would be produced by calls to #map, #select, #take, #pad_right, etc.) CPU overhead created by loose_call is bad, but unexpected memory consumption would be worse, considering Proc#call has zero memory footprint. These optimizations produce a ~5x speedup, which is still 2-4x slower than regular Proc#call.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/abstractivator/proc_ext.rb', line 84

def self.loose_call(x, args, kws={}, &block)
  return x.to_proc.call(*args) if x.is_a?(Symbol) # optimization for a typical use case
  x = x.to_proc if x.respond_to?(:to_proc)
  return x unless x.callable?

  # cache proc info for performance
  info = x.instance_variable_get(:@loose_call_info)
  unless info
    params = x.parameters
    info = LooseCallInfo.new
    info.params = params
    info.req_arity = params.count { |p| p.first == :req }
    info.total_arity = info.req_arity + params.count { |p| p.first == :opt }
    info.accepts_arg_splat = params.any? { |p| p.first == :rest }
    accepts_kw_splat = params.any? { |p| p.first == :keyrest }
    has_kw_args = params.any? { |(type, name)| (type == :key || type == :keyreq) && !name.nil? }
    info.requires_kw_customization = (has_kw_args || kws.any?) && !accepts_kw_splat
    if info.requires_kw_customization
      opt_key_names = info.params.select { |(type, name)| type == :key && !name.nil? }.map(&:value)
      req_key_names = info.params.select { |(type, name)| type == :keyreq && !name.nil? }.map(&:value)
      info.all_key_names = opt_key_names + req_key_names
      info.kw_padding = req_key_names.hash_map { nil }
    end
    x.instance_variable_set(:@loose_call_info, info)
  end

  # customize args
  unless info.accepts_arg_splat
    args = args.take(info.total_arity) if args.size > info.total_arity
    args = args.pad_right(info.req_arity) if args.size < info.req_arity
  end

  # customize keywords
  if info.requires_kw_customization
    kws = info.kw_padding.merge(kws.select { |k| info.all_key_names.include?(k) })
  end

  if kws.any?
    x.call(*args, **kws, &block)
  else
    x.call(*args, &block)
  end
end

.loosen_varargs!(args) ⇒ Object



128
129
130
131
132
133
134
135
# File 'lib/abstractivator/proc_ext.rb', line 128

def self.loosen_varargs!(args)
  if args.size == 1 && args.first.is_a?(Array)
    real_args = args.first
    args.clear
    args.concat(real_args)
    nil
  end
end

.pipe(*procs) ⇒ Object

composes procedures in reverse order. useful for applying a series of transformations. pipe(f, g, h) returns the procedure proc { |x| h.call(g.call(f.call(x))) }



39
40
41
# File 'lib/abstractivator/proc_ext.rb', line 39

def self.pipe(*procs)
  Proc.compose(*procs.reverse)
end

.pipe_value(value, *procs) ⇒ Object

makes a pipeline transform as with Proc::pipe and applies it to the given value.



45
46
47
# File 'lib/abstractivator/proc_ext.rb', line 45

def self.pipe_value(value, *procs)
  Proc.pipe(*procs).call(value)
end

Instance Method Details

#compose(other) ⇒ Object

composes this procedure with another procedure f.compose(g) ==> proc { |x| f.call(g.call(x)) }



24
25
26
# File 'lib/abstractivator/proc_ext.rb', line 24

def compose(other)
  proc{|x| self.call(other.call(x))}
end

#proxy_call(*args, **kws, &block) ⇒ Object



61
62
63
64
65
66
67
68
69
# File 'lib/abstractivator/proc_ext.rb', line 61

def proxy_call(*args, **kws, &block)
  if accepts_keywords
    call(*args, **kws, &block)
  elsif kws.any?
    call(*(args + [kws]), &block)
  else
    call(*args, &block)
  end
end

#reverse_argsObject

returns a version of the procedure with the argument list reversed



55
56
57
58
59
# File 'lib/abstractivator/proc_ext.rb', line 55

def reverse_args
  proc do |*args, &block|
    self.call(*args.reverse, &block)
  end
end