April 20, 2022·4 min read·engineeringperlserializationscalars

Perl exposes scalar creation history for serializers

Perl 5.35.11 adds experimental predicates for string-origin and number-origin scalars. They answer a serialization question that ordinary numeric validation cannot answer.

A serializer must sometimes distinguish a number from a string containing the same digits. Perl code ordinarily has little reason to care about that distinction because scalar values can participate in string and numeric operations. A format consumed by another language may require an explicit choice. Perl 5.35.11 adds experimental builtin::created_as_string and builtin::created_as_number functions to expose the relevant creation history. The release notes identify serialization and encoding as their intended uses.

The release history records April 20 as the release date. These functions arrive in the development series with deliberately narrow meanings. They do not determine whether a value can be used in a string operation or whether its text is a valid number. Those questions differ from how a scalar's value originated.

Creation is different from convertibility

The builtin documentation shipped with the release defines the string predicate in terms of the most recent assignment or modification. A string literal qualifies, as do string-like operations such as concatenation and regular-expression operations. The numeric predicate recognizes numeric literals and numeric operations such as addition.

use strict;
use warnings;
use builtin qw(created_as_string created_as_number);

my $identifier = "42";
my $count = 42;

print created_as_string($identifier) ? "string\n" : "other\n";
print created_as_number($count) ? "number\n" : "other\n";

The example declares two values whose ordinary printed digits agree. Their origins differ, and the predicates expose that difference. The experimental warnings remain relevant because both the overall builtin mechanism and the individual functions are experimental in this version.

A string containing decimal digits can still be usable in a numeric operation even though created_as_number returns false for that string. Conversely, a number can be usable as text without satisfying created_as_string. These predicates consequently do not replace validation of external input. A request field whose contents must follow a numeric grammar needs a check of those contents, regardless of the scalar's origin.

I value Perl's automatic conversions when writing a compact transformation of text. Serialization introduces a different requirement because the receiving format may attach meaning to a distinction Perl permits ordinary expressions to ignore. An explicit predicate is useful when that distinction is part of the output contract.

The history describes the current value

The wording about the most recent assignment or modification matters. These functions do not promise a permanent biography of a variable. A variable can receive a string and later receive the result of arithmetic. The value produced by that arithmetic has numeric origins even though an earlier value in the same variable came from text.

use strict;
use warnings;
use builtin qw(created_as_string created_as_number);

my $value = "42";
my $numeric_value = $value + 0;

print created_as_string($value) ? "text input\n" : "other\n";
print created_as_number($numeric_value) ? "numeric result\n" : "other\n";

Here the second value comes from addition. The example makes the conversion explicit instead of asking the serializer to infer application intent from matching digits. A schema may establish that an identifier must remain a string or that a quantity must be numeric. In that case, application code can construct the appropriate value before encoding it.

The new predicates provide evidence about the scalar representation's origin, not a schema for the data. An encoder still needs policies for the other values Perl can represent. Making a string-or-number decision explicit does not establish how references, undefined values, or application objects should appear in an external format. For example, treating every sequence of digits as a quantity would erase the distinction demonstrated by the identifier and count above. The predicates let an encoder retain that distinction without making the contents of the string its only evidence.

Objects and booleans do not become ordinary numbers

The documentation says both predicates return false for references, including objects. That remains true for an object that overloads conversion to a string or a number. Its examples include a URI object for stringification and a Math::BigInt object for numeric conversion. Supporting those objects in an encoder therefore requires a separate object policy.

Booleans and undef also return false for both predicates. This makes the pair unsuitable as an exhaustive two-way classification of every scalar. A false answer to the string question does not imply a true answer to the numeric question. Code that assumes the two are complements would silently assign an inappropriate output category to these other cases.

The builtin mechanism exposes plain functions, callable through fully qualified names. Importing selected names creates lexical aliases in the current compiling scope. The documentation contrasts that with adding package symbols, including the benefit that an imported utility does not accidentally appear as an object method. This is relevant for an encoder implemented as a class because convenient function names do not need to become part of its method interface.

My preference is to keep this distinction at the serialization boundary. Ordinary Perl expressions retain their useful conversions, while the encoder gains an explicit way to ask about value origins where the destination format requires it. The remaining work belongs to the encoder's documented treatment of booleans, undefined values, and objects that these two predicates intentionally do not classify.