zachdaniel

zachdaniel

Creator of Ash

Dynamically generate typespecs from module attribute list

I was hoping I could get something like this working:

defmodule FooRegistry do
  @foos [
    FirstFoo,
    SecondFoo,
    ThirdFoo
  ]
  
  @foos_by_another_name Enum.into(foos, %{}, fn foo -> {foo.another_name(), foo} end)

  @type foo [Enum.map(@foos, fn foo -> foo.t end)]

  @spec list_foos() :: [foo]
  def list_foos() do
    @foos
  end

  @spec foo_from_another_name(atom) :: foo | nil
  def foo_from_another_name(name) do
    Map.get(@foos_by_another_name, name)
  end
end

I’m not really worried about the exact syntax, but how might I go about dynamically generating a type like this?

Thanks!

Most Liked

evadne

evadne

For posterity:

defmodule Test do
  @keys ~w(a b c)a
  @type key :: unquote(Enum.reduce(@keys, &{:|, [], [&1, &2]}))
end
21
Post #5
axelson

axelson

Scenic Core Team

This is a great snippet! Here’s a function form of this:

defmodule TypeUtils do
  # https://forum.elixirforum.com/t/dynamically-generate-typespecs-from-module-attribute-list/7078/5
  def list_to_typespec(list) when is_list(list) do
    Enum.reduce(list, &{:|, [], [&1, &2]})
  end
end

Usage example:

  @statuses [:not_started, :completed]
  @type status :: unquote(TypeUtils.list_to_typespec(@statuses))
Eiji

Eiji

In both @spec and @type you need to wrap everything in unquote call. Without that instead of calling function/macro the AST of such call would be stored:

iex> quote do
iex>   union_type(@keys)
iex> end
{:union_type, [],
  [{:@, [context: Elixir, import: Kernel], [{:keys, [context: Elixir], Elixir}]}]}

__using__/1 macro is as same as any other macro. use MyModule works like require MyModule + MyModule.__using__([]).

You can also write a macro like this one:

defmodule UnionType do
  defmacro union_type({:"::", _, [{name, _, _}, data]}) do
    quote bind_quoted: [data: data, name: name] do
      @type unquote({name, [], Elixir}) :: unquote(UnionType.union_type_ast(data))
    end
  end

  def union_type_ast([item]), do: item
  def union_type_ast([head | tail]), do: {:|, [], [head, union_type_ast(tail)]}
end

defmodule Example do
  import UnionType
  @keys ~w(a b c)a
  union_type key :: @keys
end

However for this you need to add this option:

[
  # …
  locals_without_parens: [union_type: 1]
]

to .formatter.exs, because otherwise it would format your code to:

defmodule Example do
  import UnionType
  @keys ~w(a b c)a
  union_type(key :: @keys)
end

Last Post!

kenny-evitt

kenny-evitt

I just checked out the library :heart:

Dreams of transclusion in Xanadu :innocent:

Where Next?

Popular in Questions Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 49084 226
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44139 214
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New

We're in Beta

About us Mission Statement