[Proposal] Support for shorter @spec syntax

47 views
Skip to first unread message

Boris Kuznetsov

unread,
Jun 2, 2022, 10:16:53 AM6/2/22
to elixir-l...@googlegroups.com
Currently, if you want to add spec to functions, you have to use @spec with a function name to define all argument / response types:

defmodule StringHelpers do
  @spec long_word?(word()) :: boolean()
  def long_word?(word) when is_binary(word) do
    String.length(word) > 8
  end
end

I think, it would be nice to reduce "visual noise" of spec definition by allowing to omit the data we already know.

defmodule StringHelpers do
  @spec word() :: boolean()
  def long_word?(word) when is_binary(word) do
    String.length(word) > 8
  end
end

With this syntax, we can define argument and response types and automatically treat it as spec for following long_word?/1 function.

What do you think?

Also, in case of multiple arguments we can either wrap it in parentheses or just use a comma for separation.

Andrey Yugai

unread,
Jun 2, 2022, 4:59:48 PM6/2/22
to elixir-l...@googlegroups.com
I don't think there's much gain in fiddling with Elixir internals trying to modify special form, or writing new macro specifically to omit the left argument in `::/2` for function specs. Perhaps more profoundly, this would implicitly tie one spec to one function clause, which is kinda odd if you want to specify typespecs for all clauses before a function head.







-------- Original Message --------
--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/01071A82-653F-488B-B17A-8C0858418C0C%40achempion.com.

Boris Kuznetsov

unread,
Jun 2, 2022, 6:42:37 PM6/2/22
to elixir-l...@googlegroups.com
It should be possible to define multiple specs for the same function with this syntax and there is the original one which still can be used.

On 2 Jun 2022, at 23:59, 'Andrey Yugai' via elixir-lang-core <elixir-l...@googlegroups.com> wrote:

I don't think there's much gain in fiddling with Elixir internals trying to modify special form, or writing new macro specifically to omit the left argument in `::/2` for function specs. Perhaps more profoundly, this would implicitly tie one spec to one function clause, which is kinda odd if you want to specify typespecs for all clauses before a function head.

Wiebe-Marten Wijnja

unread,
Jun 2, 2022, 6:52:55 PM6/2/22
to elixir-l...@googlegroups.com

I do not think we should change the language to make specs shorter.
One could even argue that having to repeat a 'too' long function name might nudge the programmer in the direction of re-thinking the naming of the function:
Maybe there is a more descriptive name or location for the function? Maybe the function is trying to do too much and could be split up?


As a side note, if people would like to try this out in their own code,
it is possible to do so without altering the language itself:

```
defmodule AlteredSpecSyntax do
  defmacro __using__(_opts) do
    original_module = module_defining_macro(__CALLER__, :@, 1)
    quote do
      Module.put_attribute(__MODULE__, AlteredSpecSyntax.OriginalModule, unquote(original_module))
      import unquote(original_module), except: [@: 1]
      import AlteredSpecSyntax
    end
  end

  import Kernel, except: [@: 1]
  defmacro @ast do
    case ast do
      # Add special cases to all kinds of expressions you want to intercept...
      {:spec, _, expr} ->
        desugar(expr)
      # ... and pass others on unchanged
      _ ->
          original_module = Module.get_attribute(__MODULE__, AlteredSpecSyntax.OriginalModule)
        quote do
          original_module.@(unquote(ast))
        end
    end
  end


  defp desugar(expr) do
    # Do whatever you want here :-)
    expr |> Macro.to_string |> Code.format_string! |> IO.puts

    # ... and return the AST you _actually_ want the module to include from here
  end

  defp module_defining_macro(env, macro_name, arity) do
    env.macros
    |> Enum.filter(fn {_module, macros} -> macros[macro_name] == arity end)
    |> List.first({Kernel, nil})
    |> elem(0)
  end
end

# Example usage:
defmodule Foo do
  use AlteredSpecSyntax
  @spec a() :: 42
  def a(), do: 42
end

```

~Marten / Qqwy

OpenPGP_signature

eksperimental

unread,
Jun 3, 2022, 8:20:18 AM6/3/22
to elixir-l...@googlegroups.com
> @spec word() :: boolean()

Hi Boris,
So if there is a function `word/0` how would you know this spec refers
to word/1 and not word/0?
Reply all
Reply to author
Forward
0 new messages