Example 1: Dynamic Email Template Generation
Use Case: Generating HTML/text email templates for password resets with embedded variables.
Problem Without Interpolation
Constructing multi-format emails requires manual concatenation, leading to error-prone and visually noisy code:
send_reset_email(User, ResetToken) ->
HtmlBody = <<
"<html>\n",
"<body>\n",
" <h1>Password Reset for ", (User#user.name)/binary, "</h1>\n",
" <p>Click <a href=\"https://example.com/reset?token=",
(ResetToken)/binary, "\">here</a> to reset.</p>\n",
"</body>\n",
"</html>"
>>,
TextBody = <<"Password Reset for ", (User#user.name)/binary, "\n\n",
"Reset link: https://example.com/reset?token=", (ResetToken)/binary>>,
send_email(User#user.email, "Password Reset", HtmlBody, TextBody).
Solution With ~f Interpolation
Cleaner syntax with proper structure preservation:
send_reset_email(User, ResetToken) ->
HtmlBody = ~f"""
<html>
<body>
<h1>Password Reset for {User#user.name}</h1>
<p>Click <a href="https://example.com/reset?token={ResetToken}">here</a> to reset.</p>
</body>
</html>
""",
TextBody = ~f"""
Password Reset for {User#user.name}
Reset link: https://example.com/reset?token={ResetToken}
""",
send_email(User#user.email, "Password Reset", HtmlBody, TextBody).
Discussion:
- Variables (
User#user.name, ResetToken) are injected directly into the template.
- Maintains visual alignment of HTML/Text content.
- No risk of missing
<<...>> operators or commas.
Example 2: Structured Error Logging
Solution With ~f Interpolation
Direct interpolation simplifies context inclusion:
log_error(Req, Error) ->
logger:error(~f"""
Request {Req#request.id} failed: {Error}. Path: {Req#request.path}
""").
Example Output
1> Req = #request{id = ~"req_2fg8", path = ~"/user/123/profile"}.
#request{id = <<"req_2fg8">>, path = <<"/user/123/profile">>}
2> log_error(Req, ~"Permission denied").
=ERROR REPORT==== 1-Mar-2025::06:31:01.618403 ===
Request req_2fg8 failed: Permission denied. Path: /user/123/profile
ok
Discussion:
- Dynamic values (
id, path, Error) are embedded naturally.
- No need for
io_lib:format/2 or positional placeholders (~s).
Example 3: OTP code
Take this function of the otp_man_index module:
module_table() ->
% ...supressed code
["| Module name | Description | Application |\n",
"|--------------|-------------|-------------|\n",
[["| `m:", M, "` | ",ModuleDoc," | [", App,"-",Vsn,"](`e:",App,":index.html`) |\n"] ||
{M, {App,Vsn}, ModuleDoc} <- Modules],
"\n\n"].
List-based concatenation is hard to read and maintain.
Solution With ~f Interpolation
module_table() ->
% ...supressed code
~f""""
| Module name | Description | Application |
|--------------|-------------|-------------|
{<< ~f"""
| `m:{M}` | {ModuleDoc} | [{App}"-"{Vsn}](`e:{App}:index.html`) |
""" || {M, {App,Vsn}, ModuleDoc} <- Modules >>}
"""".
Example Output
This example uses a modified version of the function to accept the Modules as a param.
1> io:format("~s", [module_table([
{~"foo", {~"kernel", ~"8.2"}, ~"Core OTP functionality"},
{~"bar", {~"stdlib", ~"5.1"}, ~"Standard library utilities"}
])]).
| Module name | Description | Application |
|--------------|-------------|-------------|
| `m:foo` | Core OTP functionality | [kernel"-"8.2](`e:kernel:index.html`) |
| `m:bar` | Standard library utilities | [stdlib"-"5.1](`e:stdlib:index.html`) |
ok