NAME
String::Defer - Strings with deferred interpolation
SYNOPSIS
my $defer = String::Defer->new(\my $targ);
my $str = "foo $defer bar";
$targ = "one"; say $str; # foo one bar
$targ = "two; say $str; # foo two bar
DESCRIPTION
String::Defer
objects provide delayed interpolation. They have concat (q/./
) overloading, which means that an interpolation like
"foo $defer bar"
will itself produce a String::Defer
object, and the stringification of $defer
will be delayed until that object is stringified.
METHODS
String::Defer->new(\$scalar | \&code)
This is the usual constructor (though concat
and join
can also be seen as constructors). The argument is either a scalar ref or a code ref, unblessed, and specifies a piece of the string that should be lazily evaluated. When force
is called, a scalar ref will be dereferenced and the referent stringified; a code ref will be called with no arguments.
It currently isn't possible to pass an object with ${}
or &{}
overloading; see "BUGS" below. If you wish to defer stringification of an object with stringify overloading, you need to pass a reference to (your existing reference to) the object, like this:
my $obj = Some::Class->new(...);
my $defer = String::Defer->new(\$obj);
It currently is possible to pass a ref to a scalar which happens to be holding a bare glob, like this:
my $targ = *FOO;
my $defer = String::Defer->new(\$targ);
but that may not be the case in the future. I'd like, at some point, to support passing a globref as a filehandle, and I'm not sure it's possible to distinguish between 'a ref to a scalar variable which happens to be currently holding a glob' and 'a ref to a real glob'.
It is also possible to pass a ref to a substring of another string, like this:
my $targ = "one two three";
my $defer = String::Defer->new(\substr $targ, 4, 3);
say $defer; # two
$targ = uc $targ; say $defer; # TWO
The String::Defer
will track that substring of the target string as it changes. Before perl 5.16, the target string must be long enough at the time the reference is created for this to work correctly; this has been fixed in the development version of perl, which will be released as 5.16.
$defer->concat($str, $reverse)
Concatentate $str
onto $defer
, and return a new String::Defer
containing the result. This is the method which implements the q/./
overloading.
Passing another String::Defer
will not force the object out to a plain string. Passing any other object with string overload, however, will. If you want to defer the stringification, wrap it in a String::Defer
.
$defer->force
Stringify the object, including all its constituent pieces, and return the result as a plain string. This implements the q/""/
overload.
Note that while this returns a plain string, it leaves the object itself unaffected. You can ->force
it again later, and potentially get a different result.
String::Defer->join($with, @strs)
Join strings without forcing, and return a deferred result.
Arguments are as for CORE::join
, but while the builtin will stringify immediately and return a plain string, this will allow any of $with
or @strs
to be deferred, and will carry the deferral through to the result.
Note that this is, in fact, a constructor: it must be called as a class method, and the result will be in that class. (But see "BUGS".)
djoin $with, @strs
This is a shortcut for String::Defer->join
as an exportable function. Obviously this won't be any use if you're subclassing.
BUGS
Please report any bugs to <bug-String-Defer@rt.cpan.org>.
Bugs in perl
Assignment to an existing lexical
Under some circumstances an assignment like
my $defer = String::Defer->new(\my $targ);
my $x;
$x = "A $defer B";
will leave $x
holding a plain string rather than a String::Defer
, because perl calls stringify overloading earlier than it needed to. This happens if (and only if)
- -
-
a double-quoted string (with an interpolated
String::Defer
) is assigned to a lexical scalar; - -
-
that lexical has already been declared;
- -
-
no other operators intervene between the interpolation and the assignment;
- -
-
the interpolation has at least three pieces (so, two constant sections with a variable between them, or vice versa, or more pieces than that).
So the following are all OK:
my $x = "A $defer B"; # newly declared lexical
my %h;
$h{x} = "A $defer B"; # hash element, not lexical scalar
$x = "A $defer"; # only two pieces
$x = "" . "A $defer B"; # intervening operator
The simplest workaround is to turn at least one section of the interpolation into an explicit concatenation, or even just to concatenate an empty string as in the last example above.
This applies to state
as well as to my
variables, but not to our
globals, despite their partially lexical scope.
++
and --
The increment and decrement operators don't appear to honour the stringify overloading, and instead operate on the numerical refaddr of the object. Working aroung this in this module is a little tricky, since the calling convention of the ++
and --
overloads assume you want the object to stay an object, whereas what we want here is a plain string. +=
and -=
work correctly, and do leave you with a plain string.
Tied scalars
Before perl 5.14, tied scalars don't always honour overloading properly. A tied scalar whose FETCH
returns a String::Defer
will instead appear to contain a plain string at least the first time it is evaluated. As of 5.14, this has been fixed.
Subclassing
Subclassing is currently rather fragile. The implementation assumes the object is implemented as an array of pieces, where those pieces are either plain strings, scalar refs, or code refs, but I would like to change this to something like a ->pieces
method. While it ought to be possible to override ->force
to create an object which builds the final string differently, it's not very clear how to best handle cases like an object of one subclass being concatenated with an object of another.
x
and x=
; other string ops
The repeat ops x
and x=
currently force deferred strings. It would be better if they produced deferred results, and better still if they could do so without duplicating the contents of the internal array. (Allowing the RHS to be deferred as well might be a nice touch.)
Much the same applies to all the other string ops. While functions like substr
and reverse
can't be overloaded, they can be provided as class methods. I suspect the best way forward here will be to provide a set of subclasses of String::Defer
, each of which knows how to implement one string operation. This would mean that ->join
would no longer return a String::Defer
, but rather a String::Defer::join
with internal references to its constituent pieces.
Objects pretending to be refs
Objects with ${}
and &{}
overloads ought to be accepted as stand-ins for scalar and code refs, but currently they aren't. In part this is because I'm not sure which to give precedece to if an object implements both.
Efficiency
The implementation of both concat
and join
is rather simple, and makes no attempt to merge adjacent constant strings. Join, in particular, will return a deferred string even if passed all plain strings, which should really be fixed.
AUTHOR
Ben Morrow <ben@morrow.me.uk>
COPYRIGHT
Copyright 2011 Ben Morrow <ben@morrow.me.uk>.
Released under the BSD licence.
SEE ALSO
Scalar::Defer for a more generic but more intrusive deferral mechanism.