How to join strings in Elixir?

Viewed 99716

How do I join two strings in a list with a space, like:

["StringA", "StringB"]

becomes

"StringA StringB"
9 Answers

How each method handles nil

There are a number of methods, but knowing how it handles nil values can determine which method you should choose.

This will throw an error

iex(4)> "my name is " <> "adam"
"my name is adam"

iex(1)> "my name is " <> nil
** (ArgumentError) expected binary argument in <> operator but got: nil
    (elixir) lib/kernel.ex:1767: Kernel.wrap_concatenation/3
    (elixir) lib/kernel.ex:1758: Kernel.extract_concatenations/2
    (elixir) lib/kernel.ex:1754: Kernel.extract_concatenations/2
    (elixir) expanding macro: Kernel.<>/2
    iex:1: (file)

This will just insert a blank "" string:

iex(1)> "my name is #{nil}"
"my name is "

As will this:

iex(3)> Enum.join(["my name is", nil], " ")
"my name is "

Also consider types. With <> you don't get any free casting:

iex(5)> "my name is " <> 1
** (ArgumentError) expected binary argument in <> operator but got: 1
    (elixir) lib/kernel.ex:1767: Kernel.wrap_concatenation/3
    (elixir) lib/kernel.ex:1758: Kernel.extract_concatenations/2
    (elixir) lib/kernel.ex:1754: Kernel.extract_concatenations/2
    (elixir) expanding macro: Kernel.<>/2
    iex:5: (file)

iex(5)> "my name is #{1}"
"my name is 1"

iex(7)> Enum.join(["my name is", 1], " ")
"my name is 1"

Performance in practice seems roughly the same:

iex(22)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is " <> "adam" end) end)
{8023855, :ok}
iex(23)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is " <> "adam" end) end)
{8528052, :ok}
iex(24)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is " <> "adam" end) end)
{7778532, :ok}
iex(25)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is #{"adam"}" end) end)
{7620582, :ok}
iex(26)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is #{"adam"}" end) end)
{7782710, :ok}
iex(27)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is #{"adam"}" end) end)
{7743727, :ok}

So, really depends on if you want to crash or not when the interpolated values are nil or the wrong type.

It depends on what you're trying to do. If you're just trying to write out to a new variable, then just use either:

  • String interpolation

    a = "StringA"
    b = "StringB"
    "#{a} #{b}"
    
  • String concatentation: "StringA" <> " " <> "StringB

  • Enum.join(): ["StringA", "StringB"] |> Enum.join(" ")

However, as Uri mentioned, IOLists can also be used:

["StringA", " ", "StringB"] |> IO.iodata_to_binary

IOLists are actually going to be the most performant if you need to care about resource consumption. Big Nerd Ranch has a good write-up on performance gains w/ IOLists.

If you were OK with adding a space in your list you could treat it as an iolist:

["StringA", " ", "StringB"] |> IO.iodata_to_binary # "StringA StringB"

This gives you some performances boosts as you're not duplicating any of the strings in memory.

Consider using an IO List, if you have ["String1", "string2"] and you use iolist_to_binary/1 on it then you will copy those strings into a new string. If you have an IO list you can just output it in most cases and it will concatenate it on the port. And this is the key thing, the runtime will not need to make copies of the data so it is much more efficient than concatenation.

Related