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.