raku.gg / metaprogramming

Meta-Object Protocol

2026-04-02

Raku's Meta-Object Protocol (MOP) lets you inspect and manipulate classes, roles, and objects at runtime. Using the .^ twigil (pronounced "dot-hat"), you can query an object's methods, attributes, parents, roles, and more. This is introspection on steroids.

The .^ Twigil

The .^ twigil invokes a method on the object's metaclass rather than the object itself:
class Dog { has $.name; has $.breed; method bark() { say "Woof!" } method fetch($item) { say "Fetches $item" } } my $d = Dog.new(name => "Rex", breed => "Lab"); say $d.^name; # Dog say $d.^methods; # List of method objects say $d.^attributes; # List of attribute objects

Listing Methods

.^methods returns all methods defined on a type:
class Calculator { method add($a, $b) { $a + $b } method sub($a, $b) { $a - $b } method mul($a, $b) { $a * $b } method div($a, $b) { $a / $b } } # Just the names say Calculator.^methods(:local).map(*.name); # (add sub mul div) # Including inherited methods say Calculator.^methods.map(*.name).sort; # Includes methods from Mu, Cool, Any, etc.
The :local flag restricts to methods defined in this class only.

Inspecting Attributes

.^attributes reveals an object's internal structure:
class Person { has Str $.name; has Int $.age; has Str $.email; has @.hobbies; } for Person.^attributes(:local) -> $attr { say "Name: {$attr.name}"; say " Type: {$attr.type.^name}"; say " Has accessor: {$attr.has_accessor}"; say ""; }
Output:
Name: $!name Type: Str Has accessor: True Name: $!age Type: Int Has accessor: True Name: $!email Type: Str Has accessor: True Name: @!hobbies Type: Positional Has accessor: True

Reading Attribute Values

You can read attribute values on an instance through the MOP:
class Config { has $.host = 'localhost'; has $.port = 8080; has $.debug = False; } my $c = Config.new; for $c.^attributes(:local) -> $attr { say "{$attr.name}: {$attr.get_value($c)}"; } # $!host: localhost # $!port: 8080 # $!debug: False

Inspecting the Class Hierarchy

class Animal { } class Mammal is Animal { } class Dog is Mammal { } say Dog.^parents; # ((Mammal) (Animal)) say Dog.^parents(:all); # Full chain including Mu, Any, Cool say Dog.^mro; # Method Resolution Order # Check relationships say Dog.^isa(Animal); # True say Dog.^isa(Mammal); # True say Dog.^isa(Int); # False

Inspecting Roles

role Serializable { method serialize() { ... } } role Loggable { method log($msg) { say $msg } } class User does Serializable does Loggable { has $.name; method serialize() { "User: $!name" } } say User.^roles; # List of composed roles say User.^roles(:local); # Only directly composed roles say User.^does(Serializable); # True say User.^does(Loggable); # True

Dynamic Method Calls

Use .^find_method to look up methods dynamically:
class Greeter { method hello() { say "Hello!" } method goodbye() { say "Goodbye!" } } my $g = Greeter.new; my $method-name = 'hello'; if my $m = $g.^find_method($method-name) { $m($g); # Hello! } # Or use the indirect method call syntax $g."$method-name"(); # Hello!

Building a Generic Serializer

Here is a practical use of the MOP to build a serializer that works on any class:
sub to-hash($obj --> Hash) { my %data; for $obj.^attributes(:local) -> $attr { my $name = $attr.name.substr(2); # Remove $! prefix my $value = $attr.get_value($obj); %data{$name} = $value; } %data } sub to-json-simple($obj --> Str) { my %h = to-hash($obj); '{' ~ %h.map({ "\"{.key}\": \"{.value}\"" }).join(', ') ~ '}' } class Product { has $.name; has $.price; has $.sku; } my $p = Product.new(name => "Widget", price => 9.99, sku => "WDG-001"); say to-json-simple($p); # {"name": "Widget", "price": "9.99", "sku": "WDG-001"}

Building a Diff Tool

Compare two objects of the same type:
sub diff($a, $b) { die "Type mismatch" unless $a.^name eq $b.^name; my @changes; for $a.^attributes(:local) -> $attr { my $name = $attr.name.substr(2); my $va = $attr.get_value($a); my $vb = $attr.get_value($b); if $va ne $vb { @changes.push("$name: '$va' -> '$vb'"); } } @changes } class Server { has $.host; has $.port; has $.status; } my $before = Server.new(host => "web1", port => 80, status => "up"); my $after = Server.new(host => "web1", port => 8080, status => "down"); say "Changes:"; .say for diff($before, $after); # port: '80' -> '8080' # status: 'up' -> 'down'

Checking Method Signatures

You can inspect method parameters:
class API { method get(Str $path, :$timeout = 30) { } method post(Str $path, Hash $body, :$auth) { } } for API.^methods(:local) -> $m { say "{$m.name}:"; for $m.signature.params -> $p { next if $p.invocant; # Skip self say " {$p.name // 'anon'}: {$p.type.^name} (optional: {$p.optional})"; } }

Runtime Type Creation

The MOP can even create new types at runtime:
my $new-class = Metamodel::ClassHOW.new_type(name => 'DynamicClass'); $new-class.^add_attribute(Attribute.new( :name('$!value'), :type(Str), :package($new-class), :has_accessor, )); $new-class.^add_method('describe', method () { say "I am {self.^name} with value: {self.value}"; }); $new-class.^compose; my $obj = $new-class.new(value => "dynamic!"); $obj.describe; # I am DynamicClass with value: dynamic!
The MOP is a powerful tool for building frameworks, ORMs, serializers, and any code that needs to work generically across different types. Use it when static typing is not enough and you need your code to adapt to whatever types it encounters.