Data::Generators
WorksRandom words, strings, numbers, dates, pet names, job titles — and whole tabular datasets. The fastest way to have data before you have data.
- Version
0.1.11zef:antononcube- Depends
Statistics::Distributions,Math::SpecialFunctions- License
- Artistic-2.0
- Its own test suite
- 9 files, green
- Checked
- 2026-08-21 against Raku++ 3.5.1 (dev build) and Rakudo 2026.07
Install it #
$ rakupp install Data::Generatorszef install Data::Generators writes the same store; either installer leaves the module usable by both engines.
What it is for #
You are writing something that eats data — a parser, a report, a table widget, a load test — and you do not have the data yet. This module makes plausible fakes of most things you will want: words that are real English words, strings with a shape you specify, numbers in a range, date-times between two moments, and entire tabular datasets with named columns.
Everything here is one function call, and every function takes the count first:
use Data::Generators;
say random-word(4);
say random-pet-name(3);
say random-string(2, chars => 8, ranges => ["A".."Z", "0".."9"]);
say random-real((0, 100), 3).map(*.fmt('%.1f'));(remotely kitten roundelay ankylotic)
(Walter Sweetie Sasha)
(O72P0U5P PS4AVECI)
(12.3 87.4 55.0)Your run will print different words and numbers — that is the point. What is stable is the shape, and the pages of this handbook assert on shapes.
One thing to budget for: use Data::Generators costs about a second, on both engines — the module parses its 85,000-word list and 20,000 pet-name records at load time, before your first line runs. That is the module's design, not an engine tax (Raku++ spends ~0.9s on it, Rakudo ~1.3s). Pay it once per process, not once per call.
Words #
random-word($n) draws from a built-in list of about 85,000 English words. Four word kinds are built in: 'known' (the whole list), 'common', 'stopword', and 'any'.
use Data::Generators;
say random-word(1000).elems;
say so random-word(1000).all ~~ Str;
say random-word(1000).unique.elems > 500;1000
True
TrueThe word kinds are how you keep the fakes readable — 'common' gives you words a reader will recognise, 'stopword' the little glue words:
use Data::Generators;
my @common = random-word(5, type => 'common');
my @stop = random-word(5, type => 'stopword');
say @common.elems + @stop.elems;
say so (@common, @stop).flat.all ~~ Str;
say so random-word(200, type => 'stopword').map(*.chars).max <= 15;10
True
TrueTwo neighbours of random-word are pure fun with the same signature: random-pet-name draws from real pet-license registries (ask for species => 'Cat' or 'Dog' specifically), and random-pretentious-job-title builds titles like Regional Communications Orchestrator — useful the moment your fake dataset needs a title column nobody will mistake for production data.
use Data::Generators;
say random-pet-name(4, species => 'Cat');
say random-pretentious-job-title(2);(Polly Butchie Quinn Maggie)
(Relational Factors Representative Interactive Operations Orchestrator)Strings with a shape #
random-string takes the length in characters and the character sets to draw from — each set a range or a list of characters, mixed freely:
use Data::Generators;
my @s = random-string(100, chars => 4, ranges => ["a".."f", "0".."9"]);
say @s.elems;
say so @s.map(*.chars).all == 4;
say so @s.join.comb.all ~~ /<[a..f 0..9]>/;100
True
TrueThat ranges => ["a".."f", "0".."9"] — a list holding two Ranges — is exactly the shape that taught Raku++ two lessons at once: a Range written inside an array literal must stay a Range rather than spread into its elements, and the module's own argument check smartmatches a junction topic against a code block. Both now behave as Rakudo does; the page you are reading is built on the engine that learned it.
Numbers and date-times #
random-real draws uniformly from a range given as (min, max):
use Data::Generators;
my @r = random-real((10, 20), 1000);
say @r.elems;
say so @r.all ~~ Num;
say so ([&&] @r.map({ 10 <= $_ <= 20 }));
say 14 < @r.sum / @r.elems < 16;1000
True
True
Truerandom-date-time spans 1900–2100 by default, and takes :min/:max bounds:
use Data::Generators;
my @dt = random-date-time(size => 100);
say @dt.elems;
say so @dt.all ~~ DateTime;
say so @dt.map(*.year).all ~~ 1900..2100;
my $min = DateTime.new('2024-01-01T00:00:00Z');
my $max = DateTime.new('2024-12-31T23:59:59Z');
say so random-date-time(:$min, :$max, size => 50).map(*.year).all == 2024;100
True
True
TrueCall it as
random-date-time(size => $n)or with bounds — notrandom-date-time($n). That form dies on both engines: the module's own candidate list catches a bare integer in a signature that wants a DateTime, and its own test suite avoids it too.
Whole tabular datasets #
random-tabular-dataset is the reason this module is on load-test duty everywhere: one call gives you a list of hashes with named columns, ready to feed to anything that eats rows.
use Data::Generators;
my @tbl = random-tabular-dataset(4, <name age score>);
say @tbl.elems;
say @tbl.head.keys.sort.join(',');
say so @tbl.all ~~ Map;4
age,name,score
TrueLeft to itself it picks a random generator per column. Pass generators — a map from column name to a closure taking the row count — and each column means something:
use Data::Generators;
my @readings = random-tabular-dataset(5, <city temp>, generators => {
city => { random-word($_) },
temp => { random-real((-5, 35), $_) },
});
say @readings.elems;
say so @readings.map(*.<temp>).all ~~ Num;
say so ([&&] @readings.map({ -5 <= .<temp> <= 35 }));5
True
TrueWhere the two engines differ #
Everything above runs identically under Raku++ and Rakudo. Two things at the edges do not.
generators as a positional list dies under Rakudo 2026.07. The generators => [ &gen1, &gen2 ] list form (as opposed to the map form above) trips a "Seq already consumed" error inside the module under Rakudo, and works under Raku++. Use the map form — it is clearer anyway, and it works everywhere.
srand pins the sequence under Raku++, and does not under Rakudo — the same divergence noted on the Statistics::Distributions page: a seed makes a Raku++ run reproducible, and does not make a Rakudo run reproducible, so do not build a cross-engine test on one.
What was run to put this page here #
- Parse — every file of the distribution is parsed by Raku++ itself.
- Install —
rakupp install Data::Generators, which pullsStatistics::DistributionsandMath::SpecialFunctionswith it and runs all three test suites before marking anything installed. - Test — the distribution's own suite: 9 files, green.
- Run — every example on this page, twice under each engine, as the site is built.
Getting the suite green took five engine fixes, none of them reachable from roast: a Rat index into a lazy sequence (how Math::SpecialFunctions reads its memoised Bernoulli numbers), a Range inside an array literal spreading into its elements (how this module writes character sets), a junction topic in smartmatch keeping its junction instead of collapsing (how it validates arguments), Range not doing Positional, and pick/roll not accepting the spelled-out Whatever as a count (how it decides between drawing with and without replacement). One more surfaced while writing this page: DateTime.new($dt + $offset) read the argument as a year, which put every random date-time in the year 8.