Class: SerializableProc
- Inherits:
-
Object
- Object
- SerializableProc
- Includes:
- Marshalable
- Defined in:
- lib/serializable_proc.rb,
lib/serializable_proc/binding.rb,
lib/serializable_proc/parsers.rb,
lib/serializable_proc/isolatable.rb,
lib/serializable_proc/parsers/pt.rb,
lib/serializable_proc/parsers/rp.rb,
lib/serializable_proc/marshalable.rb
Overview
SerializableProc differs from the vanilla Proc in 2 ways:
#1. Isolated variables
By default, upon initializing, all variables (local, instance, class & global) within its context are extracted from the proc’s binding, and are isolated from changes outside the proc’s scope, thus, achieving a snapshot effect.
require 'rubygems'
require 'serializable_proc'
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
s_proc = SerializableProc.new { [x, @x, @@x, $x].join(', ') }
v_proc = Proc.new { [x, @x, @@x, $x].join(', ') }
x, @x, @@x, $x = 'ly', 'iy', 'cy', 'gy'
s_proc.call # >> "lx, ix, cx, gx"
v_proc.call # >> "ly, iy, cy, gy"
It is possible to fine-tune how variables isolation is being applied by declaring @@_not_isolated_vars within the code block:
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
s_proc = SerializableProc.new do
@@_not_isolated_vars = :global, :class, :instance, :local
[x, @x, @@x, $x].join(', ')
end
x, @x, @@x, $x = 'ly', 'iy', 'cy', 'gy'
# Passing Kernel.binding is required to avoid nasty surprises
s_proc.call(binding) # >> "ly, iy, cy, gy"
Note that it is strongly-advised to append Kernel.binding as the last parameter when invoking the proc to avoid unnecessary nasty surprises.
#2. Marshallable
No throwing of TypeError when marshalling a SerializableProc:
Marshal.load(Marshal.dump(s_proc)).call # >> "lx, ix, cx, gx"
Marshal.load(Marshal.dump(v_proc)).call # >> TypeError (cannot dump Proc)
Defined Under Namespace
Modules: Isolatable, Marshalable, Parsers Classes: Binding, CannotAnalyseCodeError, CannotSerializeVariableError
Instance Method Summary collapse
-
#==(other) ⇒ Object
Returns true if
other
is exactly the same instance, or ifother
has the same string content. -
#arity ⇒ Object
Returns the number of arguments accepted when running #call.
-
#binding ⇒ Object
:nodoc:.
-
#call(*params) ⇒ Object
(also: #[])
Just like the vanilla proc, invokes it, setting params as specified.
-
#initialize(&block) ⇒ SerializableProc
constructor
Creates a new instance of SerializableProc by passing in a code block, in the process, all referenced variables (local, instance, class & global) within the block are extracted and isolated from the current context.
-
#to_proc(binding = nil) ⇒ Object
Returns a plain vanilla proc that works just like other instances of Proc, the only difference is that the binding of variables is the same as the serializable proc, which is isolated.
-
#to_s(debug = false) ⇒ Object
Returns a string representation of itself, which is in fact the code enclosed within the initializing block.
-
#to_sexp(debug = false) ⇒ Object
Returns the sexp representation of this instance.
Methods included from Marshalable
Constructor Details
#initialize(&block) ⇒ SerializableProc
Creates a new instance of SerializableProc by passing in a code block, in the process, all referenced variables (local, instance, class & global) within the block are extracted and isolated from the current context.
SerializableProc.new {|...| block }
x = lambda { ... }; SerializableProc.new(&x)
y = proc { ... }; SerializableProc.new(&y)
z = Proc.new { ... }; SerializableProc.new(&z)
The following will only work if u have ParseTree (not available for 1.9.* & JRuby) installed:
def action(&block) ; SerializableProc.new(&block) ; end
action { ... }
Fine-tuning of variables isolation can be done by declaring @@_not_isolated_vars within the code block:
SerializableProc.new do
@@_not_isolated_vars = :global # don't isolate globals
$stdout << 'WAKE UP !!' # $stdout won't be isolated (avoid marshal error)
end
(see #call for invoking)
95 96 97 98 99 100 |
# File 'lib/serializable_proc.rb', line 95 def initialize(&block) file, line = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$/.match(block.inspect)[1..2] @file, @line, @arity = File.(file), line.to_i, block.arity @code, @sexp = Parsers::PT.process(block) || Parsers::RP.process(self.class, @file, @line) @binding = Binding.new(block.binding, @sexp[:extracted]) end |
Instance Method Details
#==(other) ⇒ Object
Returns true if other
is exactly the same instance, or if other
has the same string content.
x = SerializableProc.new { puts 'awesome' }
y = SerializableProc.new { puts 'wonderful' }
z = SerializableProc.new { puts 'awesome' }
x == x # >> true
x == y # >> false
x == z # >> true
114 115 116 117 |
# File 'lib/serializable_proc.rb', line 114 def ==(other) other.object_id == object_id or other.is_a?(self.class) && other.to_s == to_s end |
#arity ⇒ Object
Returns the number of arguments accepted when running #call. This is extracted directly from the initializing code block, & is only as accurate as Proc#arity.
Note that at the time of this writing, running on 1.8.* yields different result from that of 1.9.*:
lambda { }.arity # 1.8.* (-1) / 1.9.* (0) (?!)
lambda {|x| }.arity # 1.8.* (1) / 1.9.* (1)
lambda {|x,y| }.arity # 1.8.* (2) / 1.9.* (2)
lambda {|*x| }.arity # 1.8.* (-1) / 1.9.* (-1)
lambda {|x, *y| }.arity # 1.8.* (-2) / 1.9.* (-2)
lambda {|(x,y)| }.arity # 1.8.* (1) / 1.9.* (1)
190 191 192 |
# File 'lib/serializable_proc.rb', line 190 def arity @arity end |
#binding ⇒ Object
:nodoc:
232 233 234 |
# File 'lib/serializable_proc.rb', line 232 def binding #:nodoc: raise NotImplementedError end |
#call(*params) ⇒ Object Also known as: []
Just like the vanilla proc, invokes it, setting params as specified. Since the code representation of a SerializableProc is a lambda, expect lambda-like behaviour when wrong number of params are passed in.
SerializableProc.new{|i| (['hello'] * i).join(' ') }.call(2)
# >> 'hello hello'
In the case where variables have been declared not-isolated with @@_not_isolated_vars, invoking requires passing in Kernel.binding
as the last parameter avoid unexpected surprises:
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
s_proc = SerializableProc.new do
@@_not_isolated_vars = :global, :class, :instance, :local
[x, @x, @@x, $x].join(', ')
end
s_proc.call
# >> raises NameError for x
# >> @x is assumed nil (undefined)
# >> raises NameError for @@x (actually this depends on if u are using 1.9.* or 1.8.*)
# >> no issue with $x (since global is, after all, a global)
To ensure expected results:
s_proc.call(binding) # >> 'lx, ix, cx, gx'
222 223 224 225 226 227 228 |
# File 'lib/serializable_proc.rb', line 222 def call(*params) if (binding = params[-1]).is_a?(::Binding) to_proc(binding).call(*params[0..-2]) else to_proc.call(*params) end end |
#to_proc(binding = nil) ⇒ Object
Returns a plain vanilla proc that works just like other instances of Proc, the only difference is that the binding of variables is the same as the serializable proc, which is isolated.
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
s_proc = SerializableProc.new { [x, @x, @@x, $x].join(', ') }
x, @x, @@x, $x = 'ly', 'iy', 'cy', 'gy'
s_proc.to_proc.call # >> 'lx, ix, cx, gx'
Just like any object that responds to #to_proc, you can do the following as well:
def action(&block) ; yield ; end
action(&s_proc) # >> 'lx, ix, cx, gx'
134 135 136 137 138 139 140 |
# File 'lib/serializable_proc.rb', line 134 def to_proc(binding = nil) if binding eval(@code[:runnable], @binding.eval!(binding), @file, @line) else @proc ||= eval(@code[:runnable], @binding.eval!, @file, @line) end end |
#to_s(debug = false) ⇒ Object
Returns a string representation of itself, which is in fact the code enclosed within the initializing block.
SerializableProc.new { [x, @x, @@x, $x].join(', ') }.to_s
# >> lambda { [x, @x, @@x, $x].join(', ') }
By specifying debug
as true, the true runnable code is returned, the only difference from the above is that the variables within has been renamed (in order to provide for variables isolation):
SerializableProc.new { [x, @x, @@x, $x].join(', ') }.to_s(true)
# >> lambda { [lvar_x, ivar_x, cvar_x, gvar_x].join(', ') }
The following renaming rules apply:
-
local variable -> prefixed with ‘lvar_’,
-
instance variable -> replaced ‘@’ with ‘ivar_’
-
class variable -> replaced ‘@@’ with ‘cvar_’
-
global variable -> replaced ‘$ with ’gvar_’
162 163 164 |
# File 'lib/serializable_proc.rb', line 162 def to_s(debug = false) @code[debug ? :runnable : :extracted] end |
#to_sexp(debug = false) ⇒ Object
Returns the sexp representation of this instance. By default, the sexp represents the extracted code, if debug
specified as true, the runnable code version is returned.
SerializableProc.new { [x, @x, @@x, $x].join(', ') }.to_sexp
172 173 174 |
# File 'lib/serializable_proc.rb', line 172 def to_sexp(debug = false) @sexp[debug ? :runnable : :extracted] end |