package util;
use strict;
use warnings;
our $VERSION = '0.01';
require XSLoader;
XSLoader::load('util', $VERSION);
1;

__END__

=head1 NAME

util - Functional programming utilities with XS acceleration

=head1 SYNOPSIS

    use util qw(
        memo pipeline compose lazy force dig tap clamp identity always
        nvl coalesce first any all none
        is_ref is_array is_hash is_code is_defined
        is_empty starts_with ends_with
    );

    # Type predicates - blazing fast, compile-time optimized
    if (is_array($data)) { ... }
    if (is_hash($config)) { ... }
    if (is_code($callback)) { ... }
    if (is_defined($value)) { ... }

    # String predicates - fast direct SvPV/SvCUR access
    if (is_empty($str)) { ... }
    if (starts_with($filename, '/')) { ... }
    if (ends_with($filename, '.txt')) { ... }

    # Memoization - cache function results
    my $fib = memo(sub {
        my $n = shift;
        return $n if $n < 2;
        return $fib->($n-1) + $fib->($n-2);
    });

    # Pipelines - chain transformations
    my $result = pipeline($data,
        \&fetch,
        \&transform,
        \&process
    );

    # Lazy evaluation - defer computation
    my $expensive = lazy { heavy_computation() };
    my $result = force($expensive);

    # Safe navigation - no exceptions
    my $val = dig($hash, qw(deep nested key));

    # Null coalescing
    my $val = nvl($maybe_undef, $default);
    my $val = coalesce($a, $b, $c);  # First defined

    # List operations
    my $found = first { $_->{active} } @users;
    if (any { $_ > 10 } @numbers) { ... }
    if (all { $_->{valid} } @records) { ... }

    # Debugging helper - execute side effect, return original
    my $result = tap(sub { print "Got: $_\n" }, $value);

    # Constrain value to range
    my $clamped = clamp($value, $min, $max);

    # Identity function - returns argument unchanged
    my $same = identity($x);

    # Constant function factory
    my $get_zero = always(0);
    my $get_config = always({ debug => 1 });
    $get_zero->();  # Always returns 0

=head1 DESCRIPTION

C<util> provides functional programming utilities implemented in XS/C.

B<Custom ops> (compile-time optimization, no function call overhead):

=over 4

=item * C<identity> - eliminated entirely at compile time

=item * C<is_ref>, C<is_array>, C<is_hash>, C<is_code>, C<is_defined> - single SV flag check

=item * C<is_empty>, C<starts_with>, C<ends_with> - direct SvPV/SvCUR string access

=item * C<clamp> - inlined numeric comparison

=back

B<XS functions> (faster than pure Perl, but still have call overhead):

=over 4

=item * C<memo>, C<force>, C<dig> - significant speedups (2-5x)

=item * C<nvl>, C<coalesce> - fast null coalescing

=item * C<first>, C<any>, C<all>, C<none> - short-circuit list operations

=item * C<pipeline>, C<compose> - micro improvements (~15-20%)

=item * C<lazy>, C<tap>, C<always> - convenience with modest speedup

=back

Functions that call arbitrary Perl coderefs (C<pipeline>, C<compose>, C<tap>,
C<first>, C<any>, C<all>, C<none>) are limited by C<call_sv()> overhead and
cannot achieve the same speedups as pure data operations.

=head1 FUNCTIONS

=head2 memo

    my $cached = memo(\&expensive_function);
    my $result = $cached->($arg);

Returns a memoized version of the given function. Results are cached
based on arguments, so repeated calls with the same arguments return
instantly from the cache.

=head2 pipeline

    my $result = pipeline($initial_value, \&fn1, \&fn2, \&fn3);

Pipes a value through a series of functions, passing the result of each
function as the argument to the next. Equivalent to C<fn3(fn2(fn1($value)))>
but more readable. Provides modest speedup over pure Perl equivalent.

=head2 compose

    my $pipeline = compose(\&fn3, \&fn2, \&fn1);
    my $result = $pipeline->($value);

Creates a new function that composes the given functions right-to-left.
C<compose(\&c, \&b, \&a)> creates a function equivalent to C<sub { c(b(a(@_))) }>.

=head2 lazy

    my $deferred = lazy { expensive_computation() };

Creates a lazy value that defers computation until forced. The computation
runs at most once; subsequent forces return the cached result.

=head2 force

    my $result = force($lazy_value);

Forces evaluation of a lazy value, returning the computed result.
If the value has already been forced, returns the cached result.
Non-lazy values pass through unchanged.

=head2 dig

    my $val = dig($hashref, @keys);
    my $val = dig($hashref, 'a', 'b', 'c');  # $hashref->{a}{b}{c}

Safely traverses a nested hash structure. Returns undef if any key
is missing, without throwing an exception.

=head2 tap

    my $result = tap(\&block, $value);
    my $result = tap(sub { print "Debug: $_\n" }, $value);

Executes a side-effect block with the value (setting C<$_> and passing
as argument), then returns the original value unchanged. Useful for
debugging pipelines without affecting data flow.

=head2 clamp

    my $clamped = clamp($value, $min, $max);

Constrains a numeric value to a range. Returns C<$min> if C<$value E<lt> $min>,
C<$max> if C<$value E<gt> $max>, otherwise returns C<$value>.

=head2 identity

    my $same = identity($value);

Returns the argument unchanged. Uses compile-time optimization to
eliminate the function call entirely. Useful as a default transformer
in pipelines or when an API requires a function but you want a no-op.

=head2 always

    my $get_value = always($constant);
    $get_value->();        # Returns $constant
    $get_value->(1,2,3);   # Still returns $constant (args ignored)

Creates a function that always returns the same value, ignoring any arguments.
Useful for callbacks that need to return a fixed value.

=head2 nvl

    my $val = nvl($value, $default);

Returns C<$value> if defined, otherwise returns C<$default>. This is the
null coalescing operator found in many languages (C<??> in C#, C<//> in Perl 5.10+).

=head2 coalesce

    my $val = coalesce($a, $b, $c, ...);

Returns the first defined value from the argument list. If all arguments
are undefined, returns C<undef>.

=head2 first

    my $found = first { $_->{active} } @list;

Returns the first element in C<@list> for which the block returns true.
Sets C<$_> to each element in turn. Returns C<undef> if no element matches.
Short-circuits on first match.

=head2 any

    my $bool = any { $_ > 10 } @list;

Returns true if the block returns true for any element in C<@list>.
Short-circuits on first match.

=head2 all

    my $bool = all { $_->{valid} } @list;

Returns true if the block returns true for all elements in C<@list>.
Returns true for an empty list (vacuous truth). Short-circuits on first failure.

=head2 none

    my $bool = none { $_->{error} } @list;

Returns true if the block returns false for all elements in C<@list>.
Equivalent to C<not any { ... } @list>. Short-circuits on first match.

=head1 TYPE PREDICATES

These functions use custom ops and are replaced at compile time with
direct SV flag checks. They have zero function call overhead.

=head2 is_ref

    my $bool = is_ref($value);

Returns true if C<$value> is a reference (any type).

=head2 is_array

    my $bool = is_array($value);

Returns true if C<$value> is an array reference.

=head2 is_hash

    my $bool = is_hash($value);

Returns true if C<$value> is a hash reference.

=head2 is_code

    my $bool = is_code($value);

Returns true if C<$value> is a code reference.

=head2 is_defined

    my $bool = is_defined($value);

Returns true if C<$value> is defined (not C<undef>).

=head1 STRING PREDICATES

These functions use custom ops with direct SvPV/SvCUR access for
blazing fast string operations. They have minimal function call overhead.

=head2 is_empty

    my $bool = is_empty($value);

Returns true if C<$value> is undefined or an empty string.

=head2 starts_with

    my $bool = starts_with($string, $prefix);

Returns true if C<$string> starts with C<$prefix>. Uses direct
memcmp for speed. Returns false if either argument is undefined.

=head2 ends_with

    my $bool = ends_with($string, $suffix);

Returns true if C<$string> ends with C<$suffix>. Uses direct
memcmp for speed. Returns false if either argument is undefined.

=head1 PERFORMANCE

Benchmarks on Apple M1 (vs pure Perl equivalents):

    Function             Speedup    Notes
    --------------------------------------------------
    is_array/is_hash/    ~10-20x    Single SV flag check, no call
    is_code/is_ref/                 overhead - custom op
    is_defined

    is_empty             ~10-15x    Direct SvOK/SvCUR check
    starts_with          ~5-10x     Direct memcmp, no regex
    ends_with            ~5-10x     Direct memcmp, no regex

    identity             N/A        Eliminated at compile time
    clamp                ~5-10x     Inlined comparison - custom op

    memo (cache hit)     ~2-3x      XS hash lookup
    force (cached)       ~2x        XS with type check
    dig (4 levels)       ~4-5x      XS hash traversal
    nvl/coalesce         ~2-3x      XS defined check

    first/any/all/none   ~15-30%    Limited by call_sv
    pipeline (3 funcs)   ~15%       Limited by call_sv
    compose              ~15%       Limited by call_sv

=head1 AUTHOR

LNATION E<lt>email@lnation.orgE<gt>

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
