=encoding utf8

=head1 NAME

Sidef::Object::LazyMethod - Deferred, chainable method calls.

=head1 DESCRIPTION

A C<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 L</call> or L</run>, or implicitly, by calling I<any> other not-already-defined method name on it (see L</IMPLICIT EXECUTION>).

C<LazyMethod> objects are immutable: L</method> always returns a I<new> C<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).

C<LazyMethod> objects aren't normally created directly; they come from calling C<.method(name, *args)> or C<.methods(*args)> on I<any> Sidef object, both provided by the common base class -- see L<Sidef::Object::Object/method> and L<Sidef::Object::Object/methods>. 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 L</call>/L</run> I<can> do is extend the arguments of the I<last> step in the chain -- see L</call> for details.

=head1 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

=head1 INHERITS

Inherits methods from L<Sidef::Object::Object>.

=head1 CONSTRUCTION

=head2 new

    LazyMethod.new(call, call, ...)

Creates a C<LazyMethod> object directly from a list of raw internal call-descriptors (hashes with C<method>/C<args> keys, and an C<obj> key on the first one). This is an implementation detail used internally by L<Sidef::Object::Object/method> and by L</method> below -- user code should build chains with C<.method(name, *args)> instead of calling this directly.

=cut

=head1 CHAINING

=head2 method

    self.method(name, *args)

Returns a I<new> C<LazyMethod> with one more step appended to the end of the chain: when eventually triggered, this step will call the method named C<name>, with C<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). C<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

=cut

=head1 EXECUTION

=head2 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 I<last> step.

Any C<extra_args> given to C<call> are appended after the I<last> step's own pre-configured arguments (they do B<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

C<call> is not a separately implemented method -- it's one of the two names (see L</run>) specially recognized by the same fallback mechanism described in L</IMPLICIT EXECUTION>.

Aliases: I<run>

=cut

=head1 IMPLICIT EXECUTION

Calling I<any> method name on a C<LazyMethod> object that isn't already defined -- neither on C<LazyMethod> itself (L</new>, L</method>, L</call>, L</run>) nor inherited from L<Sidef::Object::Object> (C<class>, C<dump>, C<is_a>, and so on) -- triggers the chain via Perl's C<AUTOLOAD> mechanism: the whole chain runs exactly as L</call> would (but I<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 I<before> C<AUTOLOAD> is ever consulted, calling something like C<.class> or C<.dump> on a C<LazyMethod> object reports on the C<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.

=cut

=head1 EXAMPLES

=head2 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
    }

=head2 Method exploration

C<.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"]

=head2 Functional composition

Build up a multi-step pipeline one C<.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

=head1 IMPLEMENTATION NOTES

=over 4

=item * A C<LazyMethod> object stores the complete chain of method-call descriptors (name + arguments); only the first one also carries the original object.

=item * Each call to L</method> returns a new C<LazyMethod> with the extended chain -- C<LazyMethod> objects are immutable.

=item * Execution is sequential: the first step's method runs on the original object, and each following step runs on the previous step's result.

=item * L</call> and L</run> are the exact same underlying code path -- there is no behavioral difference between them.

=item * 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.

=back

=head1 SEE ALSO

=over 4

=item * L<Sidef::Object::Object> - defines C<.method()> and C<.methods()>, the two ways to create a C<LazyMethod>

=item * L<Sidef::Object::Lazy> - lazy evaluation over a whole iterable sequence, rather than a single chained value

=item * L<Sidef::Types::Block::Block> - blocks/closures, used throughout the examples above

=back

=cut
