Slangs in Raku
The term "Slang" is used two different ways within the Raku community:
The ~ twigil is for referring to sublanguages (called slangs). The following are useful:
| $~MAIN | the current main language (e.g., Raku statements) |
| $~Quote | the current root of quoting language |
| $~Quasi | the current root of quasiquoting language |
| $~Regex | the current root of regex language |
| $~Trans | the current root of transliteration language |
| $~P5Regex | the current root of the Perl regex language |
You augment these languages in your current lexical scope.
use MONKEY-TYPING;
augment slang Regex { # derive from $~Regex and then modify $~Regex
token backslash:std<\Y> { YY };
}
The MONKEY-TYPING pragma is a necessary prerequisite to using the augment keyword. It can be thought of as a reminder that the following code is of a type that's usually discouraged. More documentation on augment is at the augment declarator.
Many slangs are made available as modules through zef. These are useful for:
To see the existing Slang modules, we can look at:
Writing slangs is a beautiful and powerful tool, but sometimes, there's no point killing a mosquito with a flamethrower. Sometimes, it's best to see if you can achieve something slang-like with simpler tools.
Before diving into creating a slang, consider these alternatives:
The most direct way to create a custom declarator that works like built-in declarators (such as class, role, or sub) is to use the EXPORTHOW::DECLARE mechanism. This is the approach used by Red ORM for its model declarator.
The EXPORTHOW::DECLARE mechanism allows you to register a custom HOW (Higher Order Workings) class that the compiler will use when it encounters your declarator keyword. Here's a complete example:
# lib/DeclaratorOne.rakumod
# Custom declarator-one implementation
# Similar to how Red ORM implements the "model" declarator
class MetamodelX::DeclaratorOne is Metamodel::ClassHOW {}
my package EXPORTHOW {
package DECLARE {
constant declarator-one = MetamodelX::DeclaratorOne;
}
}
To use this declarator:
use DeclaratorOne;
declarator-one Foo {
has $.name;
method greet { say "Hello from $.name()!" }
}
my $foo = Foo.new(name => "World");
$foo.greet; # OUTPUT: «Hello from World!»
How it works:
This approach integrates seamlessly with Raku's grammar and type system, making your custom declarator work exactly like built-in declarators.
For a real-world example, see how Red ORM implements the model declarator in its source code.
You can also customize what class is used when attributes are created with has. This is done by exporting a custom attribute class via EXPORTHOW::DECLARE. Unlike class declarators which extend a HOW class, custom attribute classes extend the Attribute class itself.
This allows you to change the behavior of has attributes within classes that use your custom class declarator. You still use has to declare attributes, but they will be instantiated as instances of your custom attribute class instead of the default Attribute class.
Here's an example showing just the custom attribute class part (based on ValueClass):
# Custom attribute class example
# Note: This is just the attribute class part. You would also need to define
# a HOW class for the class declarator (like MetamodelX::ValueClassHOW).
class ValueClass::Attribute is Attribute {
method compose(|) {
# Custom behavior during attribute composition
# For example, validate that attributes can't be rw
die "Attributes { $.name } can't be rw on a value-class" if $.rw;
nextsame
}
method container_initializer(|c) {
# Custom initialization logic
# This can change default values or types
if $.name.starts-with: '@' {
return -> { Tuple.new }
} elsif $.name.starts-with: '%' {
return -> { ValueMap.new }
}
nextsame
}
}
# Export the attribute class via EXPORTHOW::DECLARE
# (The class declarator would also be exported here)
my package EXPORTHOW {
package DECLARE {
# constant value-class = MetamodelX::ValueClassHOW; # Not shown here
constant value-class-attr = ValueClass::Attribute;
}
}
To use this:
use ValueClass;
value-class MyClass {
has $.name; # Regular scalar attribute (with ValueClass validation)
has @.items; # This becomes a Tuple
has %.data; # This becomes a ValueMap
}
my $obj = MyClass.new(name => "Test", items => [1,2,3]);
say $obj.name; # OUTPUT: «Test»
How it works:
For a real-world example, see how ValueClass implements custom attributes in its source code.
If all you wish to do is use existing Slangs, you can stop reading now. However, if you wish to understand better how to make or modify Slangs, then the recommended reading before continuing is:
To get a good view of a nice, simple Slang, the best candidate would be the source for Slang::Lambda.
Slang::Lambda modifies the Raku grammar to make it possible to use the λ (lambda) symbol as the starter symbol of a pointy block (which is usually ->). The source is so short we can include it here:
my role RakuLambda {
token pointy-block-starter { '->' | '→' | '<->' | '↔' | 'λ' }
}
my role LegacyLambda {
token lambda { '->' | '<->' | 'λ' }
}
use Slangify:ver<0.0.4+>:auth<zef:lizmat> RakuLambda, Mu, LegacyLambda;
Then to use it, we do:
use Slang::Lambda;
say (1,2,3).map: λ $x { $x+1 } # (2 3 4)
The code is self-evident to those familiar with Raku's grammars. This is all well and good, but sometimes there's a large gap between being able to read code, and being able to write it. How did Elizabeth (the author of Slang::Lambda) know to use Slangify? How did she know what token names to declare? In her case, it was through extensive study of Raku and how it works, but in order to avoid this, we're going to step through some of these things now.
To ease the process of creating Slangs, Elizabeth Mattijsen also created the Slangify module (that's how she knew about it!) The Slangify module is a module intended for slang developers.
It abstracts the internals of slang creation and activation so that module developers of slangs have a consistent interface they can work with across current and future versions of the Raku Programming Language.
To use it you do the following:
use Slangify Foo::Grammar, Foo::Actions;
Either the Grammar or the Actions can be replaced with Mu to indicate that they should remain unchanged.
Two additional parameters, for legacy grammars and legacy actions, are also accepted.
While using Slangify is logically the last step in your code, it's worth understanding it somewhat before you begin, so that you can align your code with it. For further details, read the documentation for Slangify.
This is the point where you need to do the most digging. The Raku grammar is not small, and nor is it simple; its extensive error messages, which are great when programming, hinder the readability.
The sources for the Raku grammar are:
Unfortunately there's no substitute for digging through the grammar and locating the tokens/rules which you wish to override.
Now you need to write the slang grammar, much like Slang::Lambda. The links above to the grammar documentation should help here.
Many slangs will also need an Actions class backing them. This is also documented in the grammar documentation linked above.
You'll want to get your module published too. To do this, follow the instructions under Distributions: An Introduction and related pages.