Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do type specifications belong in .hrl files?

Do Erlang type specifications belong in .hrl files(which are loaded into any .erl file that needs them) or should they be kept in an Erlang module, and then exported (which would allow them to be used in other modules)? It seems to me that both methods allow achieve the same thing.

  • Are there any subtle differences I am missing? (I know that the contents of header files are copied into every file that uses them, essentially duplicating code at compile time).
  • Are there times where one method should be used over another?

Thanks in advance!

like image 806
Stratus3D Avatar asked Oct 16 '25 19:10

Stratus3D


2 Answers

Type specifications (i.e. -spec attributes) belong to the module where the function is defined. Period.

Type definitions (-type, -opaque), on the other hand, could be defined in .hrl files, but I think this is usually a poor decision. This would mean, that every module including the header in question would "define" the type locally. This might lead to namespace clashes, when the module already defined a type which is also defined in a header you wanted to include. Exporting types from modules instead of defining them in .hrls gives you a namespace prefix and disambiguates between types defined locally (mytype()) and by external applications/modules (yourmod:yourtype() or, sic!, yourmod:mytype()).

Usually, when writing an Erlang application or library it's best to define the type in the module that uses it (the most). For types which are exported outside the library, export them from the main library module - if the app is called myapp, then make all public types accessible like myapp:config(), myapp:some_record().

One more thing comes to my mind: Dialyzer doesn't like bare record definitions - it's advised to explicitly define types for records (so one -type for each -record). On the other hand, it's convenient to place record definitions in header files, so they can be shared between code in different places (like src/mymod.erl and test/mymod_tests.erl). In such case I'd define the record in the header file (src/mymod.hrl for a private module or include/mymod.hrl if the module is part of the application/library public interface), but still define and export the type from the module where it belongs (i.e. mymod:some_record()).

The point tkowal raised is also important. If you don't want to expose the internal structure, then the -opaque attribute is meant to do just that - it says "don't depend on this type's internal structure". So you just need to define a type with -opaque instead of -type, export it, and let Dialyzer warn you about each place in the code which accidentally builds a term of that type but doesn't state it explicitly, or about each pattern matching which tries to deconstruct that type outside the module where the type is defined.

like image 71
erszcz Avatar answered Oct 18 '25 12:10

erszcz


It does matter a bit in that specs are actually used to generate a function/module's documentation with the edoc application.

And if the right spec clause is not above the right function clause edoc will complain and the whole module's documentation generation will fail.

Please think of edoc.

like image 33
fenollp Avatar answered Oct 18 '25 14:10

fenollp