Understanding types definition, iolist

Hello
I am trying to understand types definitions in documentation https://hexdocs.pm/elixir/typespecs.html#built-in-types

Especially iolist() which is defined as

maybe_improper_list(byte() | binary() | iolist(), binary() | [])

I am not sure how to read pipe (|) and coma (,) in type definitions.

ChatGPT returned following:

:two: First Part: byte() | binary() | iolist()

This means each element in the list can be:

  • byte(): An integer from 0..255 (a single byte).
  • binary(): A binary string (<<...>>).
  • iolist(): Another iolist() (allowing nested lists).

:three: Second Part: binary() | []

This means the tail of the list can be either:

  • binary(): The list may end with a binary (improper list).
  • []: The list may end properly (normal list).

Which I’m not sure is true.
Please clarify

This is true.
Improper list is a list where the tail is not an empty list.

# proper list:
iex(1)> [1, 2 | []]
[1, 2]

# improper list
iex(2)> [1, 2 | 3]
[1, 2 | 3]

| pipe means or. Think about maybe_improper_list as about function that returns a type, so it has 2 args and that’s why , comma is used.

2 Likes

So binary() | [] means that tail of the list can be binary() or []

maybe_improper_list(byte() | binary() | iolist(), binary() | [])

But if have such list I can pattern match it

[h | t] = list

so In tail I can have binary() or []
but in the same time t is like “rest of the list without first element”
and there could be elements like byte() binary() or iolist() is it because byte() binary() and iolist() are also binary() ?

The second part describes the tail end of the entire list, not the tail of any list item. For example:

[1, 2, 3 | [] ]

In the above, the “First Part” in the type describes 1, 2, 3 and the “Second Part” describes only []. So in your example [h | t ], the type of t is the union of the “first part” and “second part” unless we know that it is the end of the list, then it is only the “second part”.

1 Like

So in maybe_improper_list(byte() | binary() | iolist(), binary() | []), the second argument describes the tail, right? But in this context, the tail is just the last element of the list?

I took a deep dive into bitstrings, binaries, strings, and the rest, but now even though I know a lot, I’m still very confused. I watched a video, did Exercism, read documentation, but as you can see, I’m still asking questions.

Nevertheless, this is clarifying a lot:

The second part describes the tail end of the entire list, not the tail of any list item.

Based on that:

  1. iolist() must have [] or binary() as last element in it right? (String is UTF-8 encoded binary so it can be string to right?)
  2. last element can be binary() so it can be also byte()? is byte() just a binary() with 1 byte?
  3. Why there are type for byte() while it is just 1 byte binary() ?
  1. Yes, a string is a subset of binary().
  2. A byte() is an integer between 0 and 255 inclusive, not a binary with a single byte.
  3. The byte() type is mostly used within lists, not within binaries.

Thanks, but when defining binary <<0,1,255>> aren’t we using byte() ? since it is 8 bit integer?

The binary “constructor” syntax, <<...>> accepts bytes, but it doesn’t produce a byte(). Try for yourself, see if <<0>> equals 0.