58

I'm just starting out with Elixir. I'm writing some tests using ExUnit for simple Enumerable functions that I am implementing myself, without using the standard Enum module.

In my tests I'm finding that whenever I reference the list [7, 8, 9], once it is printed in in stdout I am seeing the char list '\a\b\t'. Why does this sort of thing happen?

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
George Taveras
  • 590
  • 1
  • 4
  • 5
  • 4
    Please make an effort of _acccepting_ the answer below. This answer is nowadays often referenced and it deserves a green checker on the left side. – Aleksei Matiushkin Nov 25 '17 at 07:08
  • I don't think that's going to happen ([`Last seen Jun 28 '15 at 2:56`](https://stackoverflow.com/users/4863549/george-taveras)). – Sheharyar Nov 26 '18 at 03:47

3 Answers3

96

Elixir has two kinds of strings: binaries (double quoted) and character lists (single quoted). The latter variant is inherited from Erlang and is internally represented as a list of integers, which map to the codepoints of the string.

When you use functions like inspect and IO.inspect, Elixir tries to be smart and format a list of integers as a string for easy readability. However, in some cases you end up with a nonsense string just because all of the integers in your list happen to be valid codepoints. For example, the characters A through Z are represented as the integers 65 through 90 in ASCII.

iex> IO.inspect [65, 66, 67]
'ABC'

If you like to print a raw list, you can use the charlists: :as_lists option. For a full list of options fire up iex and type h Inspect.Opts.

iex> IO.inspect [65, 66, 67], charlists: :as_lists
[65, 66, 67]

With Elixir < 1.4, you can use char_lists: false.

By the way, this is not the only case where Elixir hides the underlying building blocks from you, it also happens with binaries (double quoted strings) and structs.

The deeper reason for this is that Elixir and Erlang do not have user-defined types, so there's no way to distinguish between a list and a single quoted string, because both are just lists. However, this can also be a strength in other situations. For example, it allows us to trivially serialize any data structure in Elixir and Erlang, because it can only be built from the basic building blocks that come with the language.

Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
  • Thanks @Patrick Oscity, This is the explanation I was looking for. – George Taveras May 04 '15 at 20:51
  • @Patrick Oscity: just for my curiosity, in which sense Elixir doesn't "have user-defined types"? If you really need them, you can use @ type definitely_string :: {:definitely_string, String.t} Is there any fundamental difference to e.g. Haskell? – Miroslav Prymek May 05 '15 at 17:08
  • 2
    @MiroslavPrymek the difference is that the types are only annotations in Elixir and not "really" part of the language. You can use them to do static analysis, but the actual types are dynamic, ignoring the annotations. Therefore, types can be created as annotations but there's no way to refer to these types during the runtime of the program. For example, you cannot write a function that takes any value and returns its type. During runtime, all values appear to be composed just from the basic types. Also see http://elixir-lang.org/getting-started/typespecs-and-behaviours.html – Patrick Oscity May 05 '15 at 21:42
  • 1
    However, there is also less strict definition of a type, which might be shocking to someone coming from Haskell ;-). I can't really find good words for this right now, but I'll try. You might want to read Chapter 9 "An Aside—What Are Types?" from Dave Thomas' book *Programming Elixir*. In essence, he says that although every value is just composed of primitive types, you can get quasi-types by defining functions that expect values adhering to a certain scheme. For example, a coordinate in two-dimensional space can be represented by a two-element tuple. – Patrick Oscity May 05 '15 at 21:56
  • These functions are then grouped in modules that keep all of the functions used for working with a type together. Still, there's no way to introspect this kind of typing. It all happens on a conceptual level, rather than being built on strict typing rules within the language. You'll find that this leads to strange bugs sometimes, but I have found that it is less of a problem than one might think at first. – Patrick Oscity May 05 '15 at 21:58
  • Yes, this is what I was talking about - if your world view is that Erlang/Elixir has kind-of types, then it has user-defined types. If your world view is that it has no types at all, then it does not make sense to talk about user-defined types ;) – Miroslav Prymek May 06 '15 at 11:20
  • And btw, the situation in Elixir is little bit different because of structs. You can easily write (and I'm used to it): def my_fun(s=%State{}) do do_something_with(s) end - and you have realtime typechecking in the Dave Thomas' sense of "well-structured data". – Miroslav Prymek May 06 '15 at 11:21
  • @MiroslavPrymek actually structs in Elixir are basically used like records/tagged tuples in Erlang, if I get this right. But I'm no Erlang expert. What I mean is that both languages do not have user-defined types in the "classical" sense, like in object-oriented or other functional languages. – Patrick Oscity May 06 '15 at 14:02
  • How does one do the same thing with `IO.puts`? Tried `IO.puts [65,66,67], char_lists: false`, got `(CaseClauseError) no case clause matching: {'ABC'}` – Simone Nov 24 '16 at 15:49
  • @simone I am not sure what you are trying to accomplish? `IO.puts/2` does not accept any options. – Patrick Oscity Nov 24 '16 at 16:14
17

While @Patrick's answer is absolutely correct, you can also configure IEx to always display charlists as regular lists instead of manually calling inspect every single time:

iex> IEx.configure(inspect: [charlists: :as_lists])
# => :ok

iex> [65, 66, 67]
# => [65, 66, 67]

iex> 'ABC'
# => [65, 66, 67]

Here's a full list of supported options.

You can also put this line in ~/.iex.exs in your home path, so the configuration is applied for every IEx session on your system.

Sheharyar
  • 73,588
  • 21
  • 168
  • 215
3

To disable this behaviour for your environment, create a ~/.iex.exs file to always apply the configuration:

# .iex.exs
IEx.configure(inspect: [charlists: :as_lists])

This prevents the need to provide the option to inspect or to run IEx.configure manually each time.

You can also have separate .iex scripts per directory, if you want to override the global setting for a specific project.

Adam Millerchip
  • 20,844
  • 5
  • 51
  • 74