What's the operator ... in Elixir?

can someone explain the opeartor … in elixir, I don’t see explaination in the docs.

1 Like

Do you mean this one? Kernel — Elixir v1.17.3

OP is asking about ... (3 dots).

The range creator .. is 2 dots.

3 dots could refer to Ecto.Query — Ecto v3.12.3

... will include all the bindings between the first and the last, which may be one, many or no bindings at all.

3 Likes

There is no such operator. ... is just possible variable/function name.

1 Like

As in Operators reference @ Elixir hex documentation the triple dots operator () is one of many operators Elixir is capable of parsing. That’s said not every supported operator is defined in Kernel module. This allows developers to write a custom operators without a need to override existing ones. :thinking:

For example it’s used in ecto:

from [p, ..., c] in posts_with_comments, select: {p.title, c.body}

If we turn off custom formatting rules it would look like:

from([p, ..., c] in posts_with_comments, select: {p.title, c.body})

So … we have a 2-arity macro called from. First argument is expression and second one is just using a keyword syntax. Now let’s see mentioned expression:

[p, ..., c] in posts_with_comments

It’s an in/2 operator with a list in the first argument and variable in the second one.

[p, ..., c]

We have just a list where first and last element are variables (query bindings) and the middle one is ... operator. We can preview how it looks like in macros simply by putting it in quote’s do … end block:

quote do
  ...
end

In result we can see how it’s AST (Abstract Syntax Tree) looks like:

{:..., [], []}

3-element tuple in AST is used to store in order: name, meta and arguments. The meta is not important if you don’t want to learn about metaprogramming. 3rd tuple element is an empty list which means that there are no arguments used in this operator, so we can skip both and focus on last one. :see_no_evil:

The last remaining part is a name:

:...

which is just an atom.

Ok, so we understand how operators are represent in macros. All we need to understand here is a fact that some macros are using custom expressions including custom operators as long as they are able to be parsed by Elixir’s compiler. :brain:

In metaprogramming we often use a pattern-matching to use some code in specific cases. Let’s write a simple example when we want to transform triple dots into a string:

defmodule Example do
  defmacro sample(ast) do
    transform_ast(ast)
  end

  defp transform_ast({:..., _meta, []}), do: "Triple dots operator"

  defp transform_ast(list) when is_list(list) do
    Enum.map(list, &transform_ast/1)
  end

  defp transform_ast({call_name, call_meta, call_args}) do
    {call_name, call_meta, transform_ast(call_args)}
  end

  defp transform_ast(literal_or_2_element_tuple), do: literal_or_2_element_tuple
end

Finally let’s try to use said operator:

iex> require Example
Example
iex> Example.sample(...)
"Triple dots operator"
iex> Example.sample([:a, ..., 123])
[:a, "Triple dots operator", 123]

Elixir core team does not create any end-to-end solution or frameworks to force you write a code in specific way. One of the core fundament is a flexibility, so you can extend a language by creating for example a DSL using macros (just like ecto is doing that). Once you understand it becomes a powerful feature. :muscle:

13 Likes

Yes.
its about ecto. I create a demo app with phoneix and kaffe.
in my windows box with elixir 1.15.7, I can use mix compile to compile the app.
but in ubuntu with elixir 1.17.2, with mix compile, I got compile error:

== Compilation error in file lib/kaffy/resource_query.ex ==
** (Ecto.Query.CompileError) binding list should contain only variables or {as, var} tuples, got: …
(ecto 3.10.3) expanding macro: Ecto.Query.from/2
lib/kaffy/resource_query.ex:188: Kaffy.ResourceQuery.build_query/7
(elixir 1.17.2) expanding macro: Kernel.if/2
lib/kaffy/resource_query.ex:184: Kaffy.ResourceQuery.build_query/7
(elixir 1.17.2) expanding macro: Kernel.if/2
lib/kaffy/resource_query.ex:176: Kaffy.ResourceQuery.build_query/7
could not compile dependency :kaffy, “mix compile” failed. Errors may have been logged above. You can recompile this dependency with “mix deps.compile kaffy --force”, update it with “mix deps.update kaffy” or clean it with “mix deps.clean kaffy”

the line in kaffe source is:

          from([..., r] in current_query,
                  or_where: field(r, ^f) == ^term

so I asking about what does … here mean? why it failed with V1.17.2

This seems to be the same as this issue, which can be fixed by updating Ecto to the latest version.

The reason the Elixir version had an impact is because the AST for ... changed in Elixir 1.17, which Ecto needed to account for:

# 1.16
iex> quote do: ...
{:..., [], Elixir}

# 1.17
iex> quote do: ...
{:..., [], []}
7 Likes

thank you. this solve my problem.

1 Like

Thank you. I learned a lot from this.

1 Like