Stratus3D
Stratus3D

Reputation: 4916

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.

Thanks in advance!

Upvotes: 4

Views: 815

Answers (3)

erszcz
erszcz

Reputation: 1660

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.

Upvotes: 7

fenollp
fenollp

Reputation: 2496

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.

Upvotes: 2

tkowal
tkowal

Reputation: 9289

It doesn't matter that much. You can put them, where it makes sense.

  • If your types are closely connected with record definitions and their internal structure should be known - put them in .hrl files.
  • If you don't want to expose internal structure (for example, you used proplists, but you want to change to dicts in the future), it may better to create module with accessor functions and put the type specification there with comment: "don't depend on this type internal structure". You still want to export it to make sure, that other modules use it. Even, if they are using it as a black box.

If you put something in .hrl and other modules depend on it - it is harder to change. When you put it in module, it should be easier to modify and refactor.

Upvotes: 1

Related Questions