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 timeis_ref,is_array,is_hash,is_code,is_defined- single SV flag checkis_empty,starts_with,ends_with- direct SvPV/SvCUR string accessclamp- 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 coalescingfirst,any,all,none- short-circuit list operationspipeline,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.