Is this a Elixir compiler bug or is not valid on elixir?

I’m getting some problems with the Elixir compiler with “circular dependencies”, let me give you a simple example:

defmodule Autor do
  @moduledoc false

  @enforcekeys [:books]
  defstruct [:books]

  @spec build([%Book{}]) :: struct
  def build(books) do
    %Autor
    {
        books: books
    }
  end
end

defmodule Book do
  @moduledoc false

  @enforcekeys [:authors]
  defstruct [:authors]
  
   @spec build([%Autor{}]) :: struct
  def build(authors) do
    %Autor
    {
        authors: authors
    }
  end
end

When I compile it gives an error:

** (UndefinedFunctionError) function Book.__struct__/0 is undefined (module Book is not available)
    Book.__struct__()
    (elixir) lib/kernel/typespec.ex:797: Kernel.Typespec.typespec/3
    (elixir) lib/kernel/typespec.ex:966: anonymous fn/4 in Kernel.Typespec.typespec/3
    (elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir) lib/kernel/typespec.ex:966: Kernel.Typespec.typespec/3
    (elixir) lib/kernel/typespec.ex:1040: anonymous fn/4 in Kernel.Typespec.fn_args/4
    (elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir) lib/kernel/typespec.ex:1040: Kernel.Typespec.fn_args/4

** (CompileError)  compile error
    (iex) lib/iex/helpers.ex:173: IEx.Helpers.c/2

The same if I compile Book module

So as the title says, it’s a Elixir compiler error, or make this its ilegal on Elixir?

Greetings!

If you split these modules into separate files, you’ll get a nicer error:

Compilation failed because of a deadlock between files.
The following files depended on the following modules:

lib/book.ex => Autor
lib/author.ex => Book

Also removing typespecs but using the structs (in function head or body) seems to work fine.

Might be an issue left when closing this ticket or maybe it’s normal behavior for typespecs, someone else might have more info.

1 Like

With Elixir v1.4.4 this works:

#File: author.ex
defmodule Author do
  @enforce_keys [:books]
  defstruct [:books]

  @type t :: %Author{books: [Book.t()]} 

  @spec build(b) :: t() when b: [Book.t()]
  def build(books),
    do: %Author{ books: books }

end

.

#File: book.ex
defmodule Book do
  @enforce_keys [:authors]
  defstruct [:authors]

  @type t :: %Book{authors: [Author.t()]}

  @spec build(a) :: t() when a: [Author.t()]
  def build(authors),
    do: %Book{ authors: authors }

end
  1. It’s @enforce_keys not @enforcekeys
  2. Even though this works now you want to be really, really sure I you want to go down this road (i.e. I would not) - you’ve essentially created an Ouroboros where there is no end to a depth first search. Even in a mutable OO language this approach is ill advised as it can cause problems with serialization and lead to other unexpected defects.

In terms of your solution what is more important the Books or Authors? If for example the Books are more important don’t list the books in the Authors. Otherwise consider simply storing a list of “Book IDs” within the authors that enables you to fetch the full information on the books and store a list of “Author IDs” within the books that enables you to fetch the full information on the authors when necessary.

Don’t forget that you are now essentially dealing with “pure data” here - for example in XML cycles are dealt with by inserting references (i.e. IDs) instead of the actual data that they represent.

1 Like

Thanks you so much!

I’ll keep the recommendations in mind, again many thanks ^^/

Off topic, but I note that your struct “Autor” is misspelled. It should be Author (with an “h” as in other places in your code).

1 Like

Oh thank you for the correction! ^^