Ancient

 view release on metacpan or  search on metacpan

lib/util.pm  view on Meta::CPAN

    if (is_empty_hash($href)) { ... }
    my $len = array_len($aref);        # Direct AvFILL access
    my $size = hash_size($href);       # Direct HvKEYS access
    my $first = array_first($aref);    # Without slice overhead
    my $last = array_last($aref);      # Without slice overhead

    # String predicates - 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 with callbacks
    my $found = first(sub { $_->{active} }, \@users);
    if (any(sub { $_ > 10 }, \@numbers)) { ... }
    if (all(sub { $_->{valid} }, \@records)) { ... }

    # Specialized predicates - pure C, no callback overhead
    my $large = first_gt(\@numbers, 100);              # first > 100
    my $adult = first_ge(\@users, 'age', 18);          # first user age >= 18
    my $last_minor = final_lt(\@users, 'age', 18);     # last user age < 18
    if (any_gt(\@values, $threshold)) { ... }          # any > threshold
    if (all_ge(\@scores, 60)) { ... }                  # all >= 60
    if (none_lt(\@ages, 18)) { ... }                   # no minors

    # 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_true>, C<is_false>, C<bool> - direct SvTRUE check

=item * C<is_num>, C<is_int>, C<is_blessed>, C<is_scalar_ref>, C<is_regex>, C<is_glob> - extended type checks

=item * C<is_positive>, C<is_negative>, C<is_zero> - numeric comparisons

=item * C<is_even>, C<is_odd> - single bitwise AND

=item * C<is_between> - range check (two comparisons)

=item * C<is_empty_array>, C<is_empty_hash> - direct AvFILL/HvKEYS check

=item * C<array_len>, C<hash_size> - direct AvFILL/HvKEYS access

=item * C<array_first>, C<array_last> - direct av_fetch without slice overhead

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

=item * C<trim>, C<ltrim>, C<rtrim> - whitespace trimming

=item * C<maybe> - conditional return (if defined)

=item * C<sign> - return -1/0/1 based on sign

=item * C<min2>, C<max2> - two-value min/max

=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> - memoization and safe navigation

=item * C<nvl>, C<coalesce> - 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> - deferred evaluation and debugging

=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 performance 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.

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

    my $add5 = partial(\&add, 5);
    my $result = $add5->(3);  # add(5, 3) = 8

Creates a partially applied function with some arguments pre-bound.
The returned function, when called, prepends the bound arguments
to any new arguments.

B<Note:> Creating AND calling a partial is 125% faster than pure Perl.
However, repeatedly calling an already-created partial is ~20% slower
than a hand-written closure. Use partial when you create once and call
many times from different contexts, or for cleaner functional code.

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

    noop();           # Returns undef
    noop(1, 2, 3);    # Ignores args, returns undef

Does nothing, returns undef. Ignores all arguments. Useful as a default
callback or placeholder.

B<Note:> This returns C<undef> (not empty list) for correct behavior in
map contexts. The standalone C<noop> module returns empty list which is
~45% faster but produces different results in C<map { noop() } @list>.

=head2 stub_true, stub_false

    stub_true();      # Always returns 1
    stub_false();     # Always returns ''

Constant functions that always return true or false. Useful as default
predicates:

    my @all = grep { stub_true() } @items;   # Accepts all
    my @none = grep { stub_false() } @items; # Rejects all

=head2 stub_array, stub_hash

    my $arr = stub_array();   # Returns new []
    my $hash = stub_hash();   # Returns new {}

Factory functions that return new empty arrayrefs or hashrefs.
Each call returns a fresh reference.

=head2 stub_string, stub_zero

    stub_string();    # Returns ''
    stub_zero();      # Returns 0



( run in 0.424 second using v1.01-cache-2.11-cpan-5511b514fd6 )