Here be docs
Tags: crystal, todayilearned, programming, opensource
Published: 2022-08-18 13:44:50

The other day, while making some changes in the [Crystal](https://crystal-lang.org/) [formatter tool](https://github.com/crystal-lang/crystal/blob/master/src/compiler/crystal/tools/formatter.cr), I learned about [heredoc](https://crystal-lang.org/reference/1.5/syntax_and_semantics/literals/string.html#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`:

```crystal
<<-HERE_BE_DOC
Hello World
Beware, here be dragons!
HERE_BE_DOC
```

A `heredoc` (as any other `String`) can be assigned to a variable:

```crystal
dragons = <<-HERE_BE_DOC
Hello World
Beware, here be dragons!
HERE_BE_DOC

puts dragons
```

This example outputs:

```terminal
Hello World
Beware, here be dragons!
```

And of course, we can use them as a method's argument:

```crystal
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:
```terminal
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:

```crystal
"Hello World".size # => 11
```

but using a `heredoc`:

```crystal
puts <<-WORLD
Hello World
WORLD.size
```

```terminal
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:

```crystal
<<-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:

```crystal
<<-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](https://github.com/crystal-lang/crystal/blob/8cb915f9552cc33df7ee873a0694ef31bb0cc7f8/spec/compiler/formatter/formatter_spec.cr#L810) using the method [`assert_format`](https://github.com/crystal-lang/crystal/blob/8cb915f9552cc33df7ee873a0694ef31bb0cc7f8/spec/compiler/formatter/formatter_spec.cr#L4). 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:

```crystal
assert_format <<-BEFORE
  alias Foo=
  Bar
  BEFORE, 
  <<-AFTER
  alias Foo = Bar
  AFTER
```

and the compiler was returning:

```terminal
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:

```crystal
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`:

```crystal
assert_format "  alias Foo=\n  Bar", "  alias Foo = Bar"
```

It looks like we didn't gain much but here is a more complex [example](https://github.com/crystal-lang/crystal/blob/8cb915f9552cc33df7ee873a0694ef31bb0cc7f8/spec/compiler/formatter/formatter_spec.cr#L810), 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 <a href="https://unsplash.com/es/@clintbustrillos?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Clint Bustrillos</a> on <a href="https://unsplash.com/s/photos/dragons?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>