Elixir Hello World

It is customary to start programming language tutorials with Hello World programs. So today I’m sharing with you a Hello World in Elixir, one of my favorite programming languages (along with Ruby and Python, of course).

As you likely know, a Hello World is a very simple program that displays the phrase, Hello, World!

Our Elixir Hello World might not be too exciting but it will allow us to discuss quite a few fundamental concepts.

Hello World in Elixir

As you might expect, this is very straightforward:

IO.puts("Hello, World!")

How to run it

The easiest way to run this line of code is to launch IEx (i.e., Interactive Elixir). This will also act as a sanity test to ensure that you have installed Elixir correctly on your machine.

$ iex
Erlang/OTP 22 [erts-10.4.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.9.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 

For the best experience on Windows, including tab-based autocompletion in IEx, it’s worth passing the --werl flag to IEx or permanently enabling it by setting the environment variable IEX_WITH_WERL to true.

C:\> iex --werl

Once you type your Elixir Hello World and press enter, IEx will print out the message Hello, World! and then display the return value of the function, which is the atom :ok1 (indicating that the function executed successfully):

iex(2)> IO.puts("Hello, World!")
Hello, World!
:ok
iex(3)>

Alternatively, you could place our one-liner in a hello.exs file and execute it as a script:

$ elixir hello.exs
Hello, World!

You could also create a whole (Mix) project for this, but arguably that is overkill for our humble Hello World.

Discussion

puts is a function that prints a message and adds a newline character. write is the variant that does the same but doesn’t append a newline. They both belong to the IO module, a module that includes, as the name implies, various functions for handling input and output.

At any time, we can learn more about a function by using the h helper within IEx:

iex(3)> h IO.puts

                        def puts(device \ :stdio, item)

  @spec puts(device(), chardata() | String.Chars.t()) :: :ok

Writes item to the given device, similar to write/2, but adds a newline at the
end.

By default, the device is the standard output. It returns :ok if it succeeds.

## Examples

    IO.puts("Hello World!")
    #=> Hello World!

    IO.puts(:stderr, "error")
    #=> error

iex(4)>

Unlike languages like Python and Ruby, in Elixir you need to use a qualified call that includes the module name in order to invoke this function.

If we don’t qualify the call, we get a compile error:

iex(4)> puts("Hello, World!")
** (CompileError) iex:1: undefined function puts/1

If you wanted to omit the module name, you could import the module (i.e., import IO) beforehand, or more realistically, limit the import to the function(s) that you need:

import IO, only: [puts: 1]

puts("Hello, World!")

That puts: 1 indicates that we are specifically importing the version of the function that accepts one argument. You’ll see that function referred to as IO.puts/1 and we say that it has an arity of one or it’s a one-arity function.

The arity of a function (i.e., the number of arguments it accepts) is important because in Elixir you can have functions with the same name but different arities.

In fact, there are technically two variants of the puts function, IO.puts/1 which we just used, and IO.puts/2 which also allows us to specify an IO device as its first argument. (I say technically because they are both declared within a single function definition.)

By default, the function prints to the standard output. This can be easily verified by looking at the Elixir source code:

def puts(device \ :stdio, item) do
  :io.put_chars(map_dev(device), [to_chardata(item), ?\n])
end

As you can see, the underlying implementation makes a call to the Erlang’s IO.put_chars/2 function.

What’s worth noting here is that:

  • In Elixir, default parameters can be specified using \\.
  • The default IO device for the function is the atom :stdio which maps to :standard_io in Erlang. Basically, by default, it will print to your stdout unless you pass a different device to the function.
  • Another common value for the device is :stderr which is a shortcut for Erlang’s :standard_error.
  • When we call the function without a device (leveraging the default parameter) we are calling IO.puts/1. When we pass it a device as its first argument and a string as its second argument, we are calling IO.puts/2.

In practice, you’ll most commonly use IO.puts/2 when printing to the standard error, instead of the standard output:

iex(5)> IO.puts(:stderr, "Hello, Post-Apocalyptic World!")
Hello, Post-Apocalyptic World!
:ok

For such messages, there are also IO.warn/1 (used below) and IO.warn/2 which output to the standard error and in addition, include a stacktrace (provided by the developer in the case of IO.warn/2).

iex(6)> IO.warn("Hello, Post-Apocalyptic World!")
warning: Hello, Post-Apocalyptic World!
  (stdlib) erl_eval.erl:680: :erl_eval.do_apply/6
  (elixir) src/elixir.erl:275: :elixir.eval_forms/4
  (iex) lib/iex/evaluator.ex:257: IEx.Evaluator.handle_eval/5
  (iex) lib/iex/evaluator.ex:237: IEx.Evaluator.do_eval/3
  (iex) lib/iex/evaluator.ex:215: IEx.Evaluator.eval/3

:ok

In most cases, parentheses are optional in Elixir, so we could omit them.2

IO.puts "Hello, World!"

It’s worth noting that strings are double-quoted literals in Elixir. Unlike other languages, single-quoted literals are not an alternative way of representing strings.

In Elixir, single-quoted literals represent a related but ultimately different datatype (i.e., List), as we can verify by using the handy data type information helper i in IEx:

iex(7)> i "Hello, World!"
Term
  "Hello, World!"
Data type
  BitString
Byte size
  13
Description
  This is a string: a UTF-8 encoded binary. It's printed surrounded by
  "double quotes" because all UTF-8 encoded code points in it are printable.
Raw representation
  <<72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33>>
Reference modules
  String, :binary
Implemented protocols
  Collectable, IEx.Info, Inspect, List.Chars, String.Chars

iex(8)> i 'Hello, World!'
Term
  'Hello, World!'
Data type
  List
Description
  This is a list of integers that is printed as a sequence of characters
  delimited by single quotes because all the integers in it represent printable
  ASCII characters. Conventionally, a list of Unicode code points is known as a
  charlist and a list of ASCII characters is a subset of it.
Raw representation
  [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
Reference modules
  List
Implemented protocols
  Collectable, Enumerable, IEx.Info, Inspect, List.Chars, String.Chars
iex(9)>

Strings are encoded in UTF-8, so you can use special characters and even emojis.

iex(9)> IO.puts("Fabrizio De André")
Fabrizio De André
:ok

iex(10)> IO.puts("Hello, 🌎!")
Hello, 🌎!
:ok

It’s also possible to print a given character by specifying its UTF-8 codepoint.

iex(11)> > IO.puts("Hello, #{<<127758 :: utf8>>}!")
Hello, 🌎!
:ok

If you are familiar with Ruby, you’ll recognize the same string interpolation syntax in which the expression that needs to be evaluated is enclosed within #{}.

Windows users encountering issues related to special characters can improve their experience by changing the active console code page by executing chcp 65001 in their Command Prompt, before executing IEx.

It doesn’t get any simpler than a Hello World program, but as you can see, if you dig a little deeper, you can find out quite a few things about a given programming language.

In the next Elixir-related post, I’ll publish a similar discussion for another seemingly trivial problem: the length of a string in Elixir. It will give us an opportunity to discuss strings more deeply. Subscribe, if you don’t already, to be notified of its publication.

Footnotes

  1. Atoms are constants whose values are their own names. The value of :ok is :ok. If you are familiar with Ruby, atoms are equivalent to symbols.
  2. A glaring exception to the parentheses being optional are non-qualified/local calls with zero-arity. In those cases, parentheses are required to distinguish simple variables (e.g., user_list) from actual function calls (e.g., list_user()). To avoid ambiguity, it’s also important to include parentheses with one-arity functions within pipelines (e.g., "Hello, World!" |> IO.puts()). For stylistic recommendations of when to use parentheses and when to omit them, consider reading these two style guides (1, 2). At any rate, do not obsess over this. mix format or a properly configured editor will typically take care of applying idiomatic and consistent styling for you.

Get more stuff like this

Subscribe to my mailing list to receive similar updates about programming.

Thank you for subscribing. Please check your email to confirm your subscription.

Something went wrong.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.