NAME
Ex::Op - a drop-in replacement for Exporter with custom ops
VERSION
Version 0.01
SYNOPSIS
package My::Util;
use Ex::Op;
our @EXPORT_OK = qw(add clamp);
our %EXPORT_TAGS = (math => [qw(add clamp)]);
op add ($x, $y) { $x + $y }
op clamp ($v, $lo, $hi) { $v < $lo ? $lo : $v > $hi ? $hi : $v }
package main;
use My::Util qw(:math);
my $z = add($a, $b);
DESCRIPTION
Ex::Op is a replacement for Exporter that reads the same @EXPORT, @EXPORT_OK and %EXPORT_TAGS, and gives the module that uses it an op keyword. An op is a named sub whose calls, once compiled, skip the work of an ordinary sub call.
OPS
use Ex::Op;
op add ($x, $y) { $x + $y }
op zero () { 0 }
op name ($self) { $self->{name} }
An op is a named sub whose arguments are copied into its parameters, and which reads @_ nowhere else. op add ($x, $y) { ... } is the same sub as sub add { my ($x, $y) = @_; ... }. Calls to it that are compiled after it skip the work of an ordinary sub call. Everything else about it is an ordinary sub, so \&add, &add(...) and calls compiled before it was made all still work, and @EXPORT and @EXPORT_OK can name it.
The parameters are a list of scalars, which may be empty, and the parentheses may be left out when there are none. A missing argument leaves its parameter undefined and extra arguments are dropped.
A body that an op cannot run, for one of the reasons under "make", is an ordinary sub. "why_not" says why.
The keyword is there from perl 5.14, from use Ex::Op to the end of the enclosing block or file, and no Ex::Op ends it sooner. Where it is not, and on perl 5.10 and 5.12, "make" makes the same op. Where it is, op followed by anything but a name is not the keyword, so a sub called op can still be called as op(...).
A call in scalar, list or void context takes the faster path, and so does a return anywhere it is allowed. A call made outside every sub (at the top level of a file or of a string eval), a call made as the last thing in an eval block, and a call made from within the op itself are ordinary calls. It is the same on every perl from 5.10.
An op has no call frame of its own. When its body is compiled with the uninitialized warning on, or with no lexical warnings at all, code it calls sees it, through caller, in the place of the sub that called it, so a stack trace from inside the op names the op and leaves out its caller, and the warning names the op's own variable. When that warning is off in the body, the same code sees the op's caller, and the op is left out.
Why an op is faster than a sub
An ordinary call pushes a call frame for the sub, sets up @_ as aliases to the arguments, and enters the sub's scope. The body's first statement, my ($x, $y) = @_, then copies the arguments out of @_ into the parameters. At the end the result is copied into a new value for the caller, and the frame is taken down again, putting back everything it saved.
A call to an op does less of that. There is no frame and no @_. The arguments are copied straight into the parameters, and the body starts at its second statement. What a frame would put back afterwards is saved in one step, and when the body left nothing else to undo, as most small bodies do, the call puts it back itself at the end. A plain result is written into a value kept at the call site, so a call makes no new value to return. A result that is a reference or an object is still copied into a new value, so that nothing is kept alive after the caller lets it go.
The body itself runs exactly as it would in a sub, so the saving is the cost of the call, and the smaller the body the more of the time that is. On perl 5.42, bench/ops.pl measures add at 1.59 times the speed of a plain sub with the same body, an accessor at 1.39 times, is_even at 1.40, clamp at 1.35 and a six-parameter sum at 1.35.
make
my $ok = Ex::Op::make($name, $code);
Installs $code under $name in the calling package, or where $name says when it is qualified, and makes it an op if it can be one. Returns true when it is one. Call it in a BEGIN block, so that the calls after it are compiled knowing.
A sub stays an ordinary sub, and make returns false, when it
reads
@_after the first statement, with$_[0],shift,pop,scalar @_or any other waycalls another sub as
&name;, which passes@_oncalls
wantarrayorcaller, usesgoto, a stringevalor__SUB__in a closuremakes a closure over its own variables, or a
qrwith a code blockreturns from inside a loop, an
evalblock, asort,grepormapblock, or a block with its ownmyvariablesuses
next,lastorredowith a label, or outside a loop of its ownhas a slurpy array or hash among its parameters, has a signature, or is an lvalue sub
is_op
Ex::Op::is_op(\&add);
True when the sub is an op. A sub that has been redefined or undefined since it was made one is not.
why_not
Ex::Op::why_not(\&add); # undef for an op
# "it calls wantarray"
The reason a sub is not an op, or undef when it is one.
strict
use Ex::Op 'strict';
Makes make die, with the name and the reason, instead of returning false. It lasts to the end of the enclosing block.
EXPORTING
package My::Util;
use Ex::Op;
our @EXPORT = qw(add);
our @EXPORT_OK = qw(clamp $DEBUG @PATH %OPT *LOG);
use Ex::Op gives the package an import, the same one that use Exporter 'import' would give it. A package that already has an import keeps it. use Ex::Op 'import' is accepted, so a module can move over by changing the one word. use Ex::Op '-noimport' installs no import. Inheriting with our @ISA = ('Ex::Op') works too.
A module that uses one then imports the way it would with Exporter:
use My::Util; # @EXPORT
use My::Util qw(clamp); # the names asked for, instead of @EXPORT
use My::Util (); # nothing
A name with no sigil, or with &, is the sub. $, @ and % are the variables, and * is the whole glob. Every name asked for must be in @EXPORT or @EXPORT_OK. If any is not, nothing is imported and the use dies with Exporter's own message, one line a name:
"nope" is not exported by the My::Util module
Can't continue after import errors at script.pl line 3.
A name that starts with a digit is a version check, passed to My::Util->VERSION. Alone it imports @EXPORT, and followed by an empty string it imports nothing.
An imported sub overrides a builtin of the same name, and an imported variable satisfies use strict 'vars', as with Exporter. Importing over a different sub of the same name warns only under -w, as with Exporter.
Why it is faster than Exporter
Exporter's import is Perl code that runs on every use. For each name it joins the package and the name into a string such as "My::Util::clamp", twice, once for where the name comes from and once for where it goes, and perl then splits each string again to find the glob. A list with anything but plain names also loads Exporter::Heavy and walks the lists in Perl. Ex::Op finds each name directly in the two packages' symbol tables, which it looks up once for the whole import, and builds no names at all. When every name asked for is a plain name it already knows, it installs them straight from the import list, with nothing copied and no checks beyond the ones Exporter would make. Both remember a package's exportable names after its first import. On perl 5.42, bench/import.pl measures an import of ten names at 4.4 times Exporter's speed, @EXPORT alone at 3.3 times, a tag at 2.5 times and a pattern at 1.5 times.
Tags, negation and patterns
our %EXPORT_TAGS = (math => [qw(add clamp)], vars => [qw($DEBUG @PATH)]);
use My::Util qw(:math); # the names in a tag
use My::Util qw(:DEFAULT clamp); # @EXPORT and a name
use My::Util qw(!add); # @EXPORT less a name
use My::Util qw(:math !clamp); # a tag less a name
use My::Util qw(/^is_/); # every exportable name matching
use My::Util qw(!/^_/); # @EXPORT less the matches
The specs are read in order, each adding names or, with !, taking them away. A list that starts with a ! starts from :DEFAULT. A pattern is matched against every name in @EXPORT and @EXPORT_OK, is not anchored, and takes no flags. A tag that is not in %EXPORT_TAGS warns, and the import then dies. Every name a spec brings in is checked as a name asked for.
@EXPORT_FAIL
our @EXPORT_FAIL = qw(fork_twice);
sub export_fail {
my ($class, @names) = @_;
return grep { !can_here($_) } @names;
}
A name in @EXPORT_FAIL that is about to be imported is passed to the package's export_fail method. Any name it returns warns that it is not implemented by the My::Util module on this architecture, and the import dies. A package with no export_fail method has every such name returned.
export_fail
Ex::Op::export_fail($class, @names);
The default export_fail, which returns @names. A package that inherits from Ex::Op gets it as a method.
export_tags
export_ok_tags
our %EXPORT_TAGS = (math => [qw(add clamp)], io => [qw(slurp)]);
Ex::Op::export_tags('math'); # adds add and clamp to @EXPORT
Ex::Op::export_ok_tags('io'); # adds slurp to @EXPORT_OK
Ex::Op::export_ok_tags(); # every tag, to @EXPORT_OK
Add the names in the tags given, or in every tag, to the calling package's @EXPORT or @EXPORT_OK. A name that is not a tag is added as it is and, under -w, warns.
require_version
My::Util->require_version('1.02');
Calls My::Util->VERSION('1.02') and returns what it returns. It takes an object as well as a class.
reset_cache
Ex::Op::reset_cache('My::Util');
Ex::Op::reset_cache(); # every package
Each exporting package's names are remembered after its first import. A name that is added later is found the first time it is asked for, and a list that is replaced or changes length is read again. A name taken out of a list without changing its length can still be imported until reset_cache is called, as with Exporter. Also as with Exporter, the first import with a list removes a leading & from the names in @EXPORT and @EXPORT_OK themselves.
$Ex::Op::Verbose
When true, every import warns what it adds, takes away and installs, in Exporter's words.
export
Ex::Op::export('My::Util', 'Some::Package', @names);
My::Util->export('Some::Package', @names); # with @ISA
Exports @names from the first package into the second, with no regard for who called it.
export_to_level
Ex::Op::export_to_level('My::Util', $level, $ignored, @names);
My::Util->export_to_level($level, $ignored, @names); # with @ISA
Exports into the package $level calls up, for a module that writes its own import:
sub import {
my $class = shift;
do_something();
Ex::Op::export_to_level($class, 1, $class, @_);
}
The third argument is ignored, as it is by Exporter. When the package has an export method of its own, that method is called to do the work. An error is reported at the line that did the importing.
$Ex::Op::ExportLevel
How many calls up import exports to. It is 0 and is meant to be set with local by a module that calls another module's import on its caller's behalf.
AUTHOR
LNATION, <email at lnation.org>
BUGS
Please report any bugs or feature requests to bug-export-op at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Ex-Op.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Ex::Op
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)