Recently on IRC
someone asked how to add methods to basic types, which reminded me of an old experimental project of mine: Protocol. Its goal is to explore a structural style of interface in Raku—distinct from Raku’s nominal roles—echoing how Go’s interfaces work by method shape rather than explicit declaration.   
🧩 Structural vs. Nominal (Go Inspiration vs. Raku Today)
Raku roles are nominal: you explicitly compose (does
) a role
to promise a bundle of behavior; roles package partial behavior for reuse and are mixed in at compile- or run-time.   
Go interfaces are structural: any type whose method set matches the interface implicitly satisfies it—no prior annotation—providing “if it quacks…” flexibility with static checking.   
Structural typing differs from duck typing: structural typing is verified statically (at compile/analyze time), while duck typing relies on runtime method availability.   
📘 What Protocol Originally Did
Originally, declaring a protocol like:
use Protocol;
protocol Nameable {
method name { ... }
}
created a subset
of Any
that type-checks values by verifying the presence of a name method (the ... yada
marks a required placeholder).   
Subsets in Raku re-dispatch to their base type but enforce constraints at assignment / parameter binding; Protocol leveraged that to express a structural “has-method(s)” check without nominal composition.   
At first, providing concrete method bodies inside a protocol (i.e. anything other than yada
) was disallowed—protocols were strictly about requirements.   
🔄 Why Evolve Beyond Pure Subsets?
In practice you often want lightweight structural acceptance plus helper / default methods that build upon the required minimal surface (a pattern familiar from roles’ default methods or convenience wrappers around Go interface method sets).   
🛠️ The New Behavior: Class Generation When Helpers Exist
If a protocol now includes any concrete method bodies, Protocol generates a class (instead of only a subset) so those methods can be mixed in via coercion when needed.
use Protocol;
protocol Nameable {
method name { ... } # required
method description {
[
"Type: { $.^name }",
"Name: { $.name }"
].join: "\n"
}
}
This Nameable
now packages both a structural requirement (name
) and a concrete helper (description
) that becomes available after coercion/mix-in.   
🎯 Pure Structural Checking Only? Use ProtocolSubset
Need only to assert “this value has method name” (no helper methods)? Parameterize with ProtocolSubset
:
my Nameable[ProtocolSubset] $with-name = Person.new: :name<Fernando>;
This uses subset semantics: compile/runtime binding checks ensure the method’s existence, while the underlying object remains unchanged and un-augmented.   
✨ Need the Helper Methods? Use ProtocolCoerce
To use helpers like .description
, request coercion:
sub print-description(Nameable[ProtocolCoerce] $_) {
say .description;
}
ProtocolCoerce
mixes in the generated class so its concrete methods become callable—conceptually similar to mixing a role into an instance on demand.   
🧪 About the Mixed Value vs. Original Object
Raku’s mixin mechanism produces a new (reblessed) object when adding roles
/mixins
; the coerced value your function receives may differ in identity from the original variable, though it forwards behavior/state as defined by Raku’s mixin
metaobject
and caching rules.   
🧠 Design Trade-Offs (Why This Split Helps)
- Ergonomics: Structural checking (
ProtocolSubset
) validates method presence without retrofitting roles into legacy or 3rd-party types.    - Extensibility: Helper methods embedded directly reduce boilerplate vs. writing separate wrapper roles or utility subs.   
- Clarity & Safety: Explicit choice between mere validation (
ProtocolSubset
) and augmentation (ProtocolCoerce
) avoids hidden behavior changes.    - Leverage Existing Semantics: Builds atop Raku subsets, mixins, and role composition models rather than inventing a wholly foreign mechanism.   
- Parametric Pattern: Parameterization mirrors broader parametric /
mixin
patterns in the ecosystem, easing conceptual load.   
🆚 Quick Comparison Snapshot
Goal | Raku Role (Nominal) | Go Interface (Structural) | Protocol |
---|---|---|---|
Require methods | Consumer does role; nominal link |
Any type with matching method set | Subset check: structural (ProtocolSubset ) |
Add helper methods | Role can define defaults | Helpers usually on concrete type | Concrete methods in protocol ⇒ coercible class |
Retroactively apply w/out editing source | Mix role (instance/class) | Implicit satisfaction | Structural subset + optional coercion |
Avoid nominal coupling | No (explicit name) | Yes (implicit) | Yes (shape-based check) |
Opt-in behavior augmentation | Role mixin | Not via interface itself | ProtocolCoerce parameter |
Each cell reflects documented capabilities of roles, subsets, mixins, and structural interfaces.   
💬 Structural Interfaces in Go (Deeper Dive)
A Go value satisfies an interface automatically if it implements the interface’s method set—no implements clause—enabling decoupled design and interface-oriented APIs; this is a compile-time structural check distinct from dynamic duck typing.   
Community discussions and educational material reiterate the static structural nature (and its contrast with duck typing) emphasizing that interface satisfaction is derived from shape not explicit declarations.   
🧪 Example Recap (Putting It Together)
- Define structural requirements (and optionally helpers) in a protocol.
- Use
Nameable[ProtocolSubset]
where you just need a guarantee the argument responds to .name. - Use
Nameable[ProtocolCoerce]
when you also want.description
(or other helper methods) to exist via a safe mixin/coercion process. These steps leverage Raku’s subset validation and mixin metaobject to approximate Go-like structural acceptance with optional augmentation.   
🏁 Conclusion 🎉
Protocol aims to blend Go-style structural typing’s flexibility with Raku’s powerful role, subset, and mixin machinery—cleanly separating validation (ProtocolSubset
) from augmentation (ProtocolCoerce
) so you can retrofit interfaces onto existing code while progressively layering reusable helper behavior.   
Top comments (0)