NAME
Sidef::Object::LazyMethod - Deferred, chainable method calls.
DESCRIPTION
A LazyMethod object records a sequence of method calls (names and arguments) without running any of them. The very first call in the chain also carries the object it will eventually be called on; every later call in the chain runs on whatever the previous one returned. Nothing actually executes until the chain is triggered -- either explicitly, via "call" or "run", or implicitly, by calling any other not-already-defined method name on it (see "IMPLICIT EXECUTION").
LazyMethod objects are immutable: "method" always returns a new LazyMethod with one more step appended, leaving the original chain untouched. This also means a chain can safely be triggered more than once -- each run replays the whole sequence of calls from scratch (assuming the underlying object and methods involved have no side effects that would make repeated calls behave differently).
LazyMethod objects aren't normally created directly; they come from calling .method(name, *args) or .methods(*args) on any Sidef object, both provided by the common base class -- see "method" in Sidef::Object::Object and "methods" in Sidef::Object::Object. Once created, the object supplied to that very first call is fixed for the lifetime of the chain: there's no way to later swap in a different starting object and replay the same chain against it. What extra arguments given to "call"/"run" can do is extend the arguments of the last step in the chain -- see "call" for details.
SYNOPSIS
# Build a chain with the 'method' method, then run it with 'call'
var lazy = [1, 2, 3, 4, 5].method('map', {|x| x * 2 })
lazy = lazy.method('grep', {|x| x > 5 })
lazy = lazy.method('sum')
say(lazy.call) #=> 30
# Or chain and execute in one expression
say([1, 2, 3, 4, 5].method('map', {|x| x ** 2 }).method('sum').call) #=> 55
# Using '.methods()' to get a Hash of every method, pre-bound to the object
var array_methods = [1, 2, 3].methods
say(array_methods{:sum}.call) #=> 6
INHERITS
Inherits methods from Sidef::Object::Object.
CONSTRUCTION
new
LazyMethod.new(call, call, ...)
Creates a LazyMethod object directly from a list of raw internal call-descriptors (hashes with method/args keys, and an obj key on the first one). This is an implementation detail used internally by "method" in Sidef::Object::Object and by "method" below -- user code should build chains with .method(name, *args) instead of calling this directly.
CHAINING
method
self.method(name, *args)
Returns a new LazyMethod with one more step appended to the end of the chain: when eventually triggered, this step will call the method named name, with args, on whatever the previous step produced (or, if this is the very first step in the chain, on the object the chain was originally created from). self itself is left unchanged.
var lazy = [1, 2, 3, 4, 5].method('map', {|x| x ** 2 })
lazy = lazy.method('grep', {|x| x > 10 })
lazy = lazy.method('sum')
say(lazy.call) #=> 41 # 16 + 25
EXECUTION
call
self.call(*extra_args)
Runs every step of the chain in order -- the first step's method is called on the chain's original object, and each following step's method is called on the previous step's result -- and returns the result of the last step.
Any extra_args given to call are appended after the last step's own pre-configured arguments (they do not go to the first step, even though the first step is where the chain's object was set):
var array_methods = [1, 2, 3].methods
# array_methods{:map} is a chain whose only step is 'map', with no args yet
say(array_methods{:map}.method('sum').call({|x| x * 2 })) #=> 12
# 'map' runs first with no block (its own configured args), THEN
# 'sum' runs with the block passed to call() appended to its own args
call is not a separately implemented method -- it's one of the two names (see "run") specially recognized by the same fallback mechanism described in "IMPLICIT EXECUTION".
Aliases: run
IMPLICIT EXECUTION
Calling any method name on a LazyMethod object that isn't already defined -- neither on LazyMethod itself ("new", "method", "call", "run") nor inherited from Sidef::Object::Object (class, dump, is_a, and so on) -- triggers the chain via Perl's AUTOLOAD mechanism: the whole chain runs exactly as "call" would (but without forwarding any arguments to the last step), and then the requested method is called, with whatever arguments were given, on that final result.
var lazy = [1, 2, 3].method('map', {|x| x * 2 })
# These are equivalent:
say(lazy.call.sum) # explicit: run the chain, then call .sum
say(lazy.sum) # implicit: 'sum' isn't a chain method, so it
# triggers execution and is then called on the result
Because inherited methods are resolved normally before AUTOLOAD is ever consulted, calling something like .class or .dump on a LazyMethod object reports on the LazyMethod object itself (its class, its internal structure) rather than triggering the chain -- only names with no existing definition anywhere in the inheritance chain fall through to this behavior.
EXAMPLES
Deferred computation
Build a chain without running it, and only trigger it when the result is actually needed:
func create_processor(data) {
data.method('grep', {|x| x.is_prime }) \
.method('map', {|x| x ** 2 }) \
.method('sum')
}
var lazy_result = create_processor(1..1000)
# No computation has happened yet
if (user_wants_result) {
say(lazy_result.call) # now it executes
}
Method exploration
.methods() returns every available method pre-bound to the object, letting you look up and try one by name:
var str_methods = "hello".methods
say(str_methods.keys.sort.first(10))
say(str_methods{:uc}.call) #=> "HELLO"
say(str_methods{:reverse}.call) #=> "olleh"
say(str_methods{:chars}.call) #=> ["h", "e", "l", "l", "o"]
Functional composition
Build up a multi-step pipeline one .method() call at a time:
var process = (1..20).method('grep', {|x| x.is_even }) \
.method('map', {|x| x ** 2 }) \
.method('grep', {|x| x > 50 }) \
.method('sum')
say(process.call) # sum of even squares greater than 50
IMPLEMENTATION NOTES
A
LazyMethodobject stores the complete chain of method-call descriptors (name + arguments); only the first one also carries the original object.Each call to "method" returns a new
LazyMethodwith the extended chain --LazyMethodobjects are immutable.Execution is sequential: the first step's method runs on the original object, and each following step runs on the previous step's result.
"call" and "run" are the exact same underlying code path -- there is no behavioral difference between them.
There's no way to swap out the original object bound at chain creation and re-run the same chain against a different starting value.
SEE ALSO
Sidef::Object::Object - defines
.method()and.methods(), the two ways to create aLazyMethodSidef::Object::Lazy - lazy evaluation over a whole iterable sequence, rather than a single chained value
Sidef::Types::Block::Block - blocks/closures, used throughout the examples above