On the surface, Elixir string interpolation looks identical to what we know from Ruby:
But the similarities soon break down:
What’s going on? It’s a difference in philosophy.
In Ruby, string interpolation implicitly calls the
#to_s method (with no arguments) on the value. So these are equivalent:
#to_s convention in Ruby is very inclusive. Almost everything in Ruby implements it. Either a class comes with its own implementation that provides a somewhat meaningful string representation (such as
Time#to_s giving values like
"2016-01-05 18:20:41 +0100"), or it inherits
Object#to_s with a less meaningful representation (like
BasicObject and its descendants may be missing a
There is some interesting nuance to Ruby’s
Kernel#String, and similar coercion methods for other types. If you’re interested, you can read all about it in Avdi Grimm’s Confident Ruby, or research it online.
By default, it handles strings, atoms (including
false and module name aliases like
String – which are all just atoms behind the scenes), integers, floats, and some lists. That’s it.
So, quite intentionally, Elixir will not implicitly convert just anything to a string.
This is the philosophical difference. For anything that doesn’t have an obviously meaningful string representation, Elixir wants you to be explicit.
This is why we couldn’t just interpolate the
ArgumentError struct above, or any other struct, or indeed tuples or maps.
So how can I interpolate my tuple, map, struct, or some lists?
The simplest thing is to use
It behaves quite like
#inspect in Ruby – even to the extent that any representation that can’t be evaluated as code starts with a
If we want to get fancy, we could implement the
String.Chars protocol for one of our structs. This lets us use plain interpolation (without explicitly calling a function like
inspect), and it also lets us control the string representation.
It’s quite simple:
Oh, and I mentioned that
String.Chars handles “some lists”.
These are the delightful char data lists – representing a string as a lists of strings, integer codepoints or other such lists.
String.Chars implementation for lists will call
List.to_string/1, which mostly consists of calling through to
IO.chardata_to_string/1, when given a list, calls through to that very same Erlang function.