Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are module signatures in Haskell?

I have recently found Haskell's feature called "module signatures". As I have discovered they are put in .hsig files and begin with signature keyword instead of module. The example syntax of such a file may look like

signature Str where

data Str
empty :: Str
append :: Str -> Str -> Str

However, I cannot imagine how and why one would use them. Could you explain me which problems do they solve and how to properly make use of them?

They strongly remind me the module system that one can see in OCaml (link), which also has modules signatures and separate implementations, but I can't decide how close are these two concepts. Is it somehow related?

like image 389
radrow Avatar asked Mar 09 '19 14:03

radrow


People also ask

What is a module in Haskell?

A Haskell module is a collection of related functions, types and typeclasses. A Haskell program is a collection of modules where the main module loads up the other modules and then uses the functions defined in them to do something. Having code split up into several modules has quite a lot of advantages.

What is HSIG file?

hsig extension and their syntax is the same as for Haskell with one minor difference (the file starts with the signature keyword instead of the module keyword). It would be nice to have Haskell syntax highlighting for such files as well as detecting them as Haskell files.

Which function is used to find type signature in Haskell?

Since the type of elem y z is Bool , this matches with the function (&&) x , and thus the type of (&&) x (elem y z) is thus Bool .

Is Haskell modular?

A Haskell program consists of a collection of modules. A module in Haskell serves the dual purpose of controlling name-spaces and creating abstract data types.


1 Answers

They are related to the OCaml module system, but with some important differences:

  • Signatures are defined within the language (in .hsig files) but unlike in OCaml they are not instantiated within the language. Instead, the package manager controls instantiation (currently, only Cabal provides that). Modules never know if they are importing an abstract signature or an actual module.

  • Implementation modules know nothing about signatures and do not not reference them directly. Any existing module can implement a signature if the definitions happen to be compatible.

  • Instantiation is triggered by a coincidence of module name and signature name in the dependencies of some compilation unit (executable, library, test suite...) When the names coincide, a process called "signature matching" takes place that verifies that the types and definitions are compatible.

The "happy path" is that in your program you depend on some library having a signature "hole", and also on another library that provides an implementation module with the same name. Then signature matching happens automatically. When the names don't match, or we need multiple instantiations of the signature-using library, we have to rename signatures and/or modules in the mixins section of the Cabal file.


As for why module signatures might be useful, consider bytestring, the most popular library by far for handling binary data in Haskell. But there are others, for example stdio with its Bytes type.

Suppose you are writing your own library that uses binary data, and you don't want to force your users into either stdio or bytestring. What are your choices?

  • One would be to create something like a Bytelike class and parameterize all your functions with it. You would also need to add a type parameter to every data type that contains bytes.
  • Another would be to create a signature that defines an abstract binary data type and all the operations that are required of it. Your library would make use of the signature, and remain "indefinite" until the user depends both on your library and a suitable implementation when creating his own libraries.

From the perspective of the user, the typeclass solution is unsatisfactory. The user knows that he wants to use either ByteString or Bytes, just one of them. The decision will not depend on some runtime flag and will remain constant across the extent of his program. And yet he has to deal with a more complex API that reminds him of that already decided issue at every turn.

It's better if he makes the decision once, writes it in his .cabal file, and deals with a simpler API from then onwards.

like image 81
danidiaz Avatar answered Oct 03 '22 00:10

danidiaz