The other day, while making some changes in the Crystal formatter tool, I learned about heredoc and how to use multiple heredocs
as methods' arguments.
Let's start with ...
What is a Heredoc?
The documentation says:
A here document or heredoc can be useful for writing strings spanning over multiple lines.
And continues explaining how they are defined:
A heredoc is denoted by
<<-
followed by a heredoc identifier [...]. The heredoc starts in the following line and ends with the next line that contains only the heredoc identifier.
How to use heredocs?
The following is an example on how to define a heredoc
:
<<-HERE_BE_DOC
Hello World
Beware, here be dragons!
HERE_BE_DOC
A heredoc
(as any other String
) can be assigned to a variable:
dragons = <<-HERE_BE_DOC
Hello World
Beware, here be dragons!
HERE_BE_DOC
puts dragons
This example outputs:
Hello World
Beware, here be dragons!
And of course, we can use them as a method's argument:
def string_length(str : String)
str.size
end
puts string_length <<-HERE_BE_DOC
Hello World
Beware, here be dragons!
HERE_BE_DOC
This example outputs:
42
Note: It keeps original newlines and spaces! 🤩
Heredoc and methods
So, if a heredoc
is a way of defining a String
, then a heredoc
responds to all String
methods (because it is a String). The last example shows exactly this when using the method #size
.
So, let's try the following example:
"Hello World".size # => 11
but using a heredoc
:
puts <<-WORLD
Hello World
WORLD.size
Error: Unterminated heredoc: can't find "WORLD" anywhere before the end of file
Oh! It cannot find the end of the heredoc
(meaning the compiler "is reading" WORLD.size
as an entire word) 🤔 ... wait 🤓💡 let's fix it like this:
<<-WORLD
Hello World
WORLD
.size # => 11
It worked 🤓🎉 but this solution feels more like a hack. Let's go to the documentation to find the correct (although not so intuitive) way of writing our example:
After the heredoc identifier, and in that same line, anything that follows continues the original expression that came before the heredoc. It's as if the end of the starting heredoc identifier is the end of the string.
So the following should work:
<<-WORLD.size # => 11
Hello World
WORLD
Great! It's working!
(We should pin this as it might be helpful later. 📌)
Using multiple heredocs
As I was saying at the beginning of this post, I was writing some tests using the method assert_format
. Let's focus on the first two arguments of this method: the first argument is the input for the formatter and the second is the expected result after formatting the input.
Since we are formatting source code, using heredoc
would be really handy (because we can use indentation and newlines making the code to be formatted easy to read), except for one single detail: the incorrect way I was trying to use it 🙈
I wrote:
assert_format <<-BEFORE
alias Foo=
Bar
BEFORE,
<<-AFTER
alias Foo = Bar
AFTER
and the compiler was returning:
Error: Unterminated heredoc: can't find "BEFORE" anywhere before the end of file
This sounds familiar, right? Remember the WORLD.size
example? Well, we have the same problem here with BEFORE,
.
And the solution is the same described in the docs. We should pass both heredocs
to the method like this:
assert_format <<-BEFORE, <<-AFTER
alias Foo=
Bar
BEFORE
alias Foo = Bar
AFTER
It's working! 🎉
The crux of the matter was in the first line assert_format <<-BEFORE, <<-AFTER
. Remember the docs:
It's as if the end of the starting heredoc identifier is the end of the string.
Heredoc vs String
Finally, here is the same example but using strings
instead of heredocs
:
assert_format " alias Foo=\n Bar", " alias Foo = Bar"
It looks like we didn't gain much but here is a more complex example, with multiple newlines
and indentations.
Farewell and see you later
We have learned about Heredoc, how we can use Heredoc's methods, and how to use multiple heredocs
as arguments to a method.
All the above and no dragons were harmed while writing this post! 😁
Hope you enjoyed it! 😃
Photo by Clint Bustrillos on Unsplash
Top comments (0)