(Protocol.UndefinedError) protocol Enumerable not implemented for %Mastery.Core.Template{} - meaning

Hi,

I am following a book - Designing Elixir Systems With OTP.

I am runing my tests and I don’t really understand where my mistake comes from. I will be grateful for explanation and help.

All tests in “test/question_test.exs”, show this Error:

** (Protocol.UndefinedError) protocol Enumerable not implemented for %Mastery.Core.Template{}...

test/question_test.exs

defmodule QuestionTest do
  use ExUnit.Case
  use QuizBuilders

  test "building chooses substitutions" do
    question = build_question(generators: addition_generators([1], [2]))
    assert question.substitutions == [left: 1, right: 2]
  end

  test "function generators are called" do
    generators = addition_generators( fn -> 42 end, [0] )
    substitutions = build_question(generators: generators).substitutions

    assert Keyword.fetch!(substitutions, :left) == generators.left.()
  end

  test "building creates asked question text" do
    question = build_question(generators: addition_generators([1], [2]))

    assert question.asked == "1 + 2"
  end

  test "a random choice is made from list generators" do
    generators = addition_generators(Enum.to_list(1..9), [0])

    assert eventually_match(generators, 1)
    assert eventually_match(generators, 9)
  end

  def eventually_match(generators, answer) do
    Stream.repeatedly(fn ->
      build_question(generators: generators).substitutions
    end)
    |> Enum.find(fn substitution ->
      Keyword.fetch!(substitution, :left) == answer
    end)
  end
end

What does the rest of the error say? It looks like somewhere in your code you are passing a %Template{} struct to an Enum function, which only work on Enumerables (maps, lists, etc).

1 Like

Thanks for reply!

The whole error - ** (Protocol.UndefinedError) protocol Enumerable not implemented for %Mastery.Core.Template{category: :addition, checker: #Function<0.108004615/2 in QuizBuilders."-fun.addition_checker/2-">, compiled: {:__block__, [], [{:=, [], [{:arg0, [], EEx.Engine}, {{:., [], [{:__aliases__, [alias: false], [:String, :Chars]}, :to_string]}, [], [{{:., [line: 1], [{:__aliases__, [line: 1, alias: false], [:EEx, :Engine]}, :fetch_assign!]}, [line: 1], [{:var!, [line: 1, context: EEx.Engine, import: Kernel], [{:assigns, [line: 1], EEx.Engine}]}, :left]}]}]}, {:=, [], [{:arg1, [], EEx.Engine}, {{:., [], [{:__aliases__, [alias: false], [:String, :Chars]}, :to_string]}, [], [{{:., [line: 1], [{:__aliases__, [line: 1, alias: false], [:EEx, :Engine]}, :fetch_assign!]}, [line: 1], [{:var!, [line: 1, context: EEx.Engine, import: Kernel], [{:assigns, [line: 1], EEx.Engine}]}, :right]}]}]}, {:<<>>, [], [{:"::", [], [{:arg0, [], EEx.Engine}, {:binary, [], EEx.Engine}]}, " + ", {:"::", [], [{:arg1, [], EEx.Engine}, {:binary, [], EEx.Engine}]}]}]}, generators: %{left: [1, 2, 3, 4, 5, 6, 7, 8, 9], right: [0]}, instructions: "Add the numbers", name: :single_digit_addition, raw: "<%= @left %> + <%= @right %>"} of type Mastery.Core.Template (a struct). This protocol is implemented for the following type(s): MapSet, Range, List, Date.Range, HashDict, Map, HashSet, Stream, File.Stream, Function, GenEvent.Stream, IO.Stream

How can I tell in which part I am passing a %Template{} struct to an Enum function?

I suppose you’ll have to look through your code. You only have a few functions being tested here, so it shouldn’t be too hard. Check build_question/1, addition_generators/2, and eventually_match/2.

1 Like

I will try my best. Thanks for your time, it did help!

Can you clarify on what did you do and how did you solve your problem?

I didn’t solve it yet. I will try to find an answer and respond tomorrow :slight_smile:

śr., 11 sie 2021, 17:54 użytkownik Dimitar Panayotov via Elixir Programming Language Forum <noreply@elixirforum.com> napisał:

If I may take a stab in the dark, this part of your error

suggests that the error is firing in this test

since it’s the only place you’re generating that list, and when your generator is being passed to eventually_match/2 then build_question(_).substitutions is providing a struct to Stream.repeatedly.

As @APB9785 mentions, check the relation/interaction between addition_generators/2 and build_question\1 in eventually_match/2, and the value of substitutions.

1 Like

If every test shows the error, the issue must be in code that runs on every test.

The usual cause of this kind of error is passing a single struct to a function that’s expecting a list of structs:

def expects_a_list(templates) do
  Enum.map(templates, fn t -> ... end)
end

# but called like
one_template = %Template{...}
expects_a_list(one_template)

# can be fixed with `[]`:
expects_a_list([one_template])

# or the function can be made more flexible with `List.wrap`:
def expects_a_list_or_one(template_or_templates) do
  templates = List.wrap(template_or_templates)
  Enum.map(templates, fn t -> ... end)
end

A good place to check is anywhere in your code that you have a plural variable name (suggesting “this should be a list”) and verify the behavior, either by code inspection or adding pattern-matching.

2 Likes