Can we @derive from Enumerable for a struct?

Enum.group_by on maps works fine:

m = [
   %{row: 1, col: 1, txt: "A"}, 
   %{row: 1, col: 2, txt: "B"},
   %{row: 2, col: 1, txt: "C"},
   %{row: 2, col: 3, txt: "D"},
   %{row: 3, col: 1, txt: "X"}
]

mm = Enum.group_by(m, fn(%{row: x}) -> x end, fn(%{txt: t}) -> t end)

for {_, x} <- mm, do: x

gives [["A", "B"], ["C", "D"], ["X"]]

But if each element is a struct e.g., %BotEngine.Action{row: 1, col: 1, txt: "A"}, then it fails with:

** (Protocol.UndefinedError) protocol Enumerable not implemented for %BotEngine.Action{...}

Can we @derive from Enumerable for a struct?

I have tried this:

defmodule BotEngine.Action do
    @derive [Enumerable]
    defstruct row: 0,
              col: 0,
              txt: ""


== Compilation error on file lib/types.ex ==
** (ArgumentError) Enumerable.Any is not available, cannot derive Enumerable for BotEngine.Action
1 Like

You have to implement the enumerable protocol for your struct (or use maps) I believe :slight_smile:

http://elixir-lang.org/getting-started/protocols.html

1 Like

I think your code actually works well with maps and structs likewise, but you are not adding the struct to the list. Instead you must be replacing m with the list. Check this out:

iex(1)> defmodule BotEndine do
...(1)> defstruct row: nil, col: nil, txt: nil
...(1)> end
iex(2)> m = [
...(2)>    %{row: 1, col: 1, txt: "A"}, 
...(2)>    %{row: 1, col: 2, txt: "B"},
...(2)>    %{row: 2, col: 1, txt: "C"},
...(2)>    %{row: 2, col: 3, txt: "D"},
...(2)>    %{row: 3, col: 1, txt: "X"}
...(2)> ]
iex(4)> mm = Enum.group_by(m, fn(%{row: x}) -> x end, fn(%{txt: t}) -> t end)
%{1 => ["A", "B"], 2 => ["C", "D"], 3 => ["X"]}

So it works with a list of maps. Now let’s add a single struct to it:

iex(5)> m = [ %BotEndine{col: 1, row: 1, txt: "Z"} | m]
[%BotEndine{col: 1, row: 1, txt: "Z"}, %{col: 1, row: 1, txt: "A"},
 %{col: 2, row: 1, txt: "B"}, %{col: 1, row: 2, txt: "C"},
 %{col: 3, row: 2, txt: "D"}, %{col: 1, row: 3, txt: "X"}]
iex(7)> mm = Enum.group_by(m, fn(%{row: x}) -> x end, fn(%{txt: t}) -> t end)
%{1 => ["Z", "A", "B"], 2 => ["C", "D"], 3 => ["X"]}

This also seems to work. You must be replacing the list with a struct, and you want a list of structs.

Also: as a rule, structs do work well with functions from Map, since structs are Maps. They do not implement enumerable for the same reason.

3 Likes

@hubertlepicki, @Linuus

Thanks i have seen my error.

I do not need to @derive anything. maps and structs work almost the same.

2 Likes