NAME

util - Functional programming utilities with XS acceleration

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

DESCRIPTION

util provides functional programming utilities implemented in XS/C.

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

  • identity - eliminated entirely at compile time

  • is_ref, is_array, is_hash, is_code, is_defined - single SV flag check

  • is_empty, starts_with, ends_with - direct SvPV/SvCUR string access

  • clamp - inlined numeric comparison

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

  • memo, force, dig - significant speedups (2-5x)

  • nvl, coalesce - fast null coalescing

  • first, any, all, none - short-circuit list operations

  • pipeline, compose - micro improvements (~15-20%)

  • lazy, tap, always - convenience with modest speedup

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

FUNCTIONS

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.

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 fn3(fn2(fn1($value))) but more readable. Provides modest speedup over pure Perl equivalent.

compose

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

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

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.

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.

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.

tap

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

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

clamp

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

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

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.

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.

nvl

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

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

coalesce

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

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

first

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

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

any

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

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

all

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

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

none

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

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

TYPE PREDICATES

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

is_ref

my $bool = is_ref($value);

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

is_array

my $bool = is_array($value);

Returns true if $value is an array reference.

is_hash

my $bool = is_hash($value);

Returns true if $value is a hash reference.

is_code

my $bool = is_code($value);

Returns true if $value is a code reference.

is_defined

my $bool = is_defined($value);

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

STRING PREDICATES

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

is_empty

my $bool = is_empty($value);

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

starts_with

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

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

ends_with

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

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

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

AUTHOR

LNATION <email@lnation.org>

LICENSE

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