eeff

eeff

About ambiguity introduced in function default arguments

Hi, I am learning Elixir and recently reading the Supervisor source code, and came accross start_link/2 and start_link/3:

# https://github.com/elixir-lang/elixir/blob/v1.20.1/lib/elixir/lib/supervisor.ex#L733
def start_link(children, options) when is_list(children) do

# https://github.com/elixir-lang/elixir/blob/v1.20.1/lib/elixir/lib/supervisor.ex#L976
def start_link(module, init_arg, options \\ []) when is_list(options)

As far as I understand, the default argument in def start_link(module, init_arg, options \\ []) should cause another start_link/2 to be defined and should result in ambiguity ? Does guard helps the compiler reduce ambiguity and thus the compiler treats it as a second clause for start_link/2 ? If so, the two clauses does not need to be grouped together ?

I tried the following:

defmodule A do
  def f(a), do: "f(#{inspect(a)})"

  def f(a, b \\ []) when is_list(b), do: "f(#{inspect(a)}, #{inspect(b)})"
end

and result in a warning:

➜  /tmp iex test.ex
Erlang/OTP 27 [erts-15.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

    warning: this clause for f/1 cannot match because a previous clause at line 2 always matches
    │
  4 │   def f(a, b \\ []) when is_list(b), do: "f(#{inspect(a)}, #{inspect(b)})"
    │       ~
    │
    └─ test.ex:4:7

Interactive Elixir (1.19.0-dev) - press Ctrl+C to exit (type h() ENTER for help)

But if I changed the order of the def’s:

defmodule A do
  def f(a, b \\ []) when is_list(b), do: "f(#{inspect(a)}, #{inspect(b)})"

  def f(a), do: "f(#{inspect(a)})"
end

it results in an error:

➜  /tmp iex test.ex
Erlang/OTP 27 [erts-15.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

    error: def f/1 conflicts with defaults from f/2
    │
  4 │   def f(a), do: "f(#{inspect(a)})"
    │       ^
    │
    └─ test.ex:4:7: A.f/1

** (CompileError) test.ex: cannot compile module A (errors have been logged)
    test.ex:4: (module)

Can anybody point me to related documentation about this behavior difference ?

Marked As Solved

mudasobwa

mudasobwa

Creator of Cure

Sidenote: Please upgrade the toolchain to Elixir1.20.1 / OTP29.

The compiler explicitly handles \\ operator because Erlang does not have default argument values. It does not allow any redeclaration after \\ has been expanded. Look-behind, though, would have a ton of redundant tracking involved, decreasing performance, that’s why it does not blow up immediately.

The error you observe is raised by first-pass compiler, meeting the \\ default parameters within the function redeclaration.

The warning you observe if you swap clauses is the second-pass compiler, simply warning you about unreachable clause, it has nothing to do with \\ because there is no look-behind.

Also Liked

mudasobwa

mudasobwa

Creator of Cure

Here is the documentation:

If a function with default values has multiple clauses, it is required to create a function head (a function definition without a body) for declaring defaults — Modules and functions — Elixir v1.20.2

Just don’t try to hack the proposed syntax. Supervisor also ditched this cludge, that’s why I suggested to use the latest versions of the toolchain.

odelbos

odelbos

You need to do it like this:

defmodule A do
  def f(a, b \\ [])

  def f(a, []), do: "f(#{inspect(a)})"
  def f(a, b) when is_list(b), do: "f(#{inspect(a)}, #{inspect(b)})"
end

IO.puts A.f(5)
# Output --> f(5)

IO.puts A.f(5, [:one, :two])
# Output --> f(5, [:one, :two])
odelbos

odelbos

It’s because of the declarative f/1 order.
When we write:

def f(a, b \\ [])

I fact the compiler will expand it in two functions:

def f(a), do: f(a, [])  # <-- This is not a declarative f/1 it's the default-argument of f/2
def f(a, b) do: ...

So in the first case

defmodule A do
  def f(a), do: "f(#{inspect(a)})"     # <--- declarative f/1

  def f(a, b \\ []) when is_list(b), do: ...   # <--- will be expanded by the compiler
end

You will have two f/1 but with the declarative f/1 in first position (top-bottom) will match all clauses. So the compiler emit a warning for the unreachable expanded f/1 that handle the default-argument case of f/2.

In the second case:

defmodule A do
  def f(a, b \\ []) when is_list(b), do ...   # <--- will be expanded by the compiler
  def f(a), do: "f(#{inspect(a)})"     # <--- declarative f/1
end

The declarative f/1 is after the expanded f/1 but from the compiler’s perspective, the expanded f/1 is not a declarative f/1 function it’s the mechanism that handle the default-argument of the f/2 function. So when later the compiler encounter the declarative f/1 which is in conflict with the auto-generated f/1 it emit a compilation error.

Where Next?

Popular in Questions Top

lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
vac
Hi, I’m quite new in Elixir and I’m trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and I...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: The documentation above suggests that while ...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
dotdotdotPaul
Okay, I’m having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I’m sure I’...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36352 110
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 127089 1222
New

We're in Beta

About us Mission Statement