Import with :only option causes undefined function error due to the default argument mechanism

Here is my .iex.ex file:

alias MyApp.Repo
alias MyApp.CMS
alias MyApp.CMS.Page

import Ecto.Query, only: [from: 2]
# import Ecto.Query
# import Ecto.Query, only: [from: 1]

Then, iex -S mix

Repo.all(from(Page))
** (CompileError) iex:1: undefined function from/1

but it works with

iex(1)> Repo.all(from(Page, []))
[debug] QUERY OK source="pages" db=5.0ms decode=5.1ms
SELECT p0."id", p0."body", p0."published_at", p0."title", p0."slug", p0."views", p0."author_id", p0."category_id", p0."inserted_at", p0."updated_at" FROM "pages" AS p0 []
[
  %MyApp.CMS.Page{ ... },
  %MyApp.CMS.Page{ ... }
]

With the full import of the Ecto.Query module in the .iex.ex file, Repo.all(from(Page)) works.

Here comes the FIX

So finally I did import Ecto.Query, only: [from: 1] and Repo.all(from(Page)) works.
However, when you look at the doc of Ecto.Query, there is no function from/1.

Is it on purpose?
Is there a better way to use import only on functions with default argument ?

Tks ~

1 Like

There is a from/2 with a default argument. Default arguments are implemented as recursive function calls. eg:

def foo(opts \\ []), do: IO.inspect opts

will get compiled to code equivalent to this:

def foo(), do: foo([])

def foo(opts), ddo: IO.inspect opts

This is why you need to import from/1.

6 Likes

Hi, where could I check this on the elixir code? It would be awesome to be able to see how default params were implemented. (I tried looking at the Kernel and Kernel.SpecialForms without success)

This is mostly urban knowledge :smiley:

And I think it is even in the elixir guides described like that, though how it might be implemented is probably well distributed between the implementation of def(p) and defmodule.

1 Like

This code from elixir_def.erl seems highly relevant:

1 Like