Welcome back. In the previous lesson, you defined a protocol and implemented it for built-in types like Integer, List, and Map. Think of this as a quick reminder: protocols give you polymorphism in Elixir by dispatching on the type of the first argument. Today, you will apply the same idea to your own data types — custom structs — and see how clean and scalable this pattern becomes.
Explanation:
defprotocoldeclares theRenderablecontract with a single function,render/1. It specifies what must exist, not how it works.UserandProductare custom structs with simple fields and default values. These give you typed, documented shapes for your data.- Once you implement
Renderablefor these structs, Elixir will pick the correctrender/1based on the value’s struct type.
Note: Using structs (instead of plain maps) gives you compile-time guarantees about the shape and supports protocol dispatch.
Explanation:
- Each
defimpltiesRenderableto a specific struct. - Pattern matching like
%User{...}and%Product{...}destructures fields and makes the function explicit about the expected shape. - Both implementations provide the same API (
render/1) but produce different strings.
Note: If you call Renderable.render/1 with a type that has no implementation, Elixir raises Protocol.UndefinedError. This is a helpful guardrail during development and testing.
Explanation:
- You construct struct values and pass them to the same function:
Renderable.render/1. - Elixir dispatches to the correct implementation based on the struct, printing:
You defined a protocol, created custom structs, implemented the protocol for each struct, and exercised dynamic dispatch with a single, clean API. This pattern scales nicely: add more structs, implement the same protocol, and your system stays extensible without conditionals.
Ready to put this into practice? Let’s jump into the exercises and solidify your skills.
