TD;DR A new version of TypedStruct is available with a plugin interface and some bug fixes.

For nearly two years now, TypedStruct has helped me and other people in the Elixir community to define typed structs without writing boilerplate code. Its core functionality is fairly minimal and I aim to keep it as such.

Nonetheless, I have had some feature requests about integrating with Ecto or a lens library. While integrating with Ecto has been on my roadmap from the beginning, many projects using TypedStruct do not use Ecto. As every project has different needs, adding new features to TypedStruct would lead to controversial decisions. To conceal both the need to extend TypedStruct and keep it minimal, I went to think about a plugin system and started to effectively work on it a year ago.

Since then, I have been hired by the French Polar Institute to be system administrator—among other things—in Kerguelen Islands for the 2020 overwintering. This is a several-years-old project which has come to life, rearranging my priorities. I have been able to work on TypedStruct only sparsingly in August, then in November while at see on the Marion Dufresne in our way to Kerguelen. Here, my free time has been shared between community life and communicating with my family and friends, writing rather long descriptive emails. Hopefully I will publish a blog post about this extraordinary experience some day.

I had in mind a virtual deadline to get back on software development projets around April, when the summer campaign would end. I effectively started to crave software development at that time, then rearranged my priorities to get to it while keeping a good life balance.

So, after a chaotic year for me in the scope of software development, I am proud to annonuce TypedStruct 0.2.0 is live! Let me show you what this release contains.

Plugin interface

The biggest feature in TypedStruct 0.2.0 is the addition of a plugin interface. As a user, that means you will be able to add new features to your typed structs. For instance, to automatically generate lenses for your fields with the Lens library, you can add typed_struct_lens to your Mix dependencies and do:

defmodule MyStruct do
  use TypedStruct

  typedstruct do
    # This line enables the plugin for the struct.
    plugin TypedStructLens

    field :a_field, String.t()
    field :other_field, atom()
  end

  @spec change(t()) :: t()
  def change(data) do
    # a_field/0 is generated by TypedStructLens.
    lens = a_field()
    put_in(data, [lens], "Changed")
  end
end

As a library developer, you can create plugins by implementing the TypedStruct.Plugin behaviour.

You can use three callbacks to inject code at different steps:

  • init/1 lets you inject code where the plugin/2 macro is called,
  • field/3 lets you inject code on each field definition,
  • after_definition/1 lets you insert code after the struct and its type have been defined.

The documentation features an example using all three callbacks so that you have an idea on how to proceed.

Already existing plugins

I have released two plugins concurrently to TypedStruct 0.2.0:

I may work on an Ecto plugin at some point in the future, but feel free to start something if you do not see a typed_struct_ecto repository appearing on my GitHub profile soon enough for you.

If you create a plugin, feel free to send me an email so that I am aware of it. I may also help you if I find it useful for my own use.

No more reflection

Since its very first version, TypedStruct had provided a simple way to access information from the struct after the module had been compiled: three reflection functions named __fields__/0, __defaults__/0 and __typed__/0 were created in each module.

With the new plugin interface, these functions are not strictly needed anymore, so I have removed them. To keep the compatibility with projects using it, I provide a plugin that defines these legacy functions. If you rely on these functions, please read the Updating section.

Bug fixes

During the development of TypedStruct 0.2.0, I came accross a tiny soundness bug: the typedstruct block was not scoped. This means the field macro was available oustide of the block, for instance. This is now resolved and the typedstruct block has a scope as expected.

Two other bugs where found by :

  • fields with a default value set to nil were still enforced when it should not be the case (#14);
  • it was possible to clash with internal module attributes defined by TypedStruct (#15). These attributes are now prefixed by ts_ and are deleted after the struct has been defined.

If you find other issues with TypedStruct or even design flaws, please open an issue.

Updating

To update from TypedStruct 0.1.x, simply replace the previous version in your Mix dependencies with:

{:typed_struct, "~> 0.2.0"}

If you are not using the reflection functions, you should be fine. Otherwise, you must also add to your Mix dependencies:

{:typed_struct_legacy_reflection, "~> 1.0.0"}

and in the structs where you need the reflection functions:

defmodule MyStruct
  use TypedStruct

  typedstruct do
    # Add this line.
    plugin TypedStructLegacyReflection

    field :a_field, String.t()
    field :another_field, atom()
  end
end

Conclusion

I hope you will enjoy and make use of this plugin interface to integrate your own needs with TypedStruct. If the plugin API proves to be correctly designed, TypedStruct 0.2.0 is a good candidate to become 1.0.0 after some time.

As always, if you have some remarks or just want to add something, please send me an email.