Keyword into Struct

Hello forum

I cannot get rid of the feeling that I am missing the obvious way of doing this

  `Keyword.merge(o, __struct__: s.__struct__) |> Enum.into( Map.delete(s, :__struct__))`

BTW is it just me or does someone else share the bad small of the above working but not the below:

  `Keyword.merge(o, __struct__: s.__struct__) |> Enum.into(s)`

Alternatively

 `o |> Enum.reduce(s, fn {k, v}, a -> Map.put(a, k, v) end)`

Both do not really seem right given the target

`o |> Enum.into(s)`

To achieve this I can implement Collectable for all my structs needing it.


defmodule Collstruct do
  defmacro __using__(fields) do
    quote do
      defstruct unquote(fields)
      defimpl Collectable, for: __MODULE__ do
        def into(options) do
          { options, fn
              acc, {:cont, {k, v}} -> Map.put(acc, k, v) # Some clever checks might go here
              acc, :done           -> acc
              _,   :halt           -> :ok
          end
        }
        end
      end
    end
  end
end

I somehow fail to remember the reasoning why structs do not implement Collectable, maybe something to do with the some clever checks above?

Thanx for any insights.

Robert

1 Like

May I suggest Kernel.struct which populates a struct from an Enumerable

8 Likes

Structs do not implement collectable, because it doesn’t make sense in most cases. For example, what would happen when you did:

Enum.into([foo: :bar], MapSet.new)

given that MapSet itself is a struct? The Collectable protocol is not about structural merging, but semantical one.

As @kip indicated, Kernel.struct/2 is the right solution here.

3 Likes

Exactly what the doctor ordered. :smiley:

Thank you.
I do have difficulties to conceptionally distinguish Structs from Maps. I will have an eye on that.

Kernel.struct/2 does exactly what I need for now. Please note though that it does the same as Enum.into. If you care to discuss the conceptional choices, why would one expose Kernel.struct/2 instead of keeping structs collectables?

Maps and structs share structure (you can pattern match on both equally), they do not share behaviour (protocols).

Structs need to have a clean slate so you can customize their behaviour. If Enum.into/2 was implemented for structs, everyone would use Enum.into/2 to change structs and when they come across a struct that implements Enum.into/2 to get a different behaviour, such as MapSet, they would get confusing results.

Thank you José.

I will need to think about that one a little bit :blush:.

Robert

How best do we handle conversion of maps with nested data to nested structs?

Also can we convert a list of maps to a list of structs directly?

with struct/2 ?

There’s nothing that simply works out of the box. You may find things like the Poison.Decoder protocol or Ecto.Changeset helpful in that case.

This would be Enum.map(maps, &struct(Struct, &1))

1 Like