Liquid Haskell comes with a number of useful specifications for standard libraries. One such example is that it comes with specifications for Data.Set
.
If you import a module and a .spec
file exists, it is automatically brought into scope by Liquid Haskell. Such .spec
files contain just Liquid Haskell directives, without any code.
Liquid Haskell allows us to give refined interfaces to trusted libraries without verifying them completely.
Here is an example:
The function checked
is an uninterpreted function. Such a function can be introduced using a measure
directive with just a type signature, and no actual definition in the code.
We can still teach Liquid Haskell facts about the behaviour of such a function.
The assume
directive assumes a type without verifying it. Here, we say if and only if check
succeeds, it establishes the checked
predicate for the checked list.
We can go on to work with checked lists.
And this is rejected:
Can you fix it?
The specification for sets introduces a number of uninterpreted functions, equips the functions from the Data.Set
library with refined types mentioning these functions, and then maps these functions to SMT solver primitives being able to reason about sets (ok, that makes them no longer entirely uninterpreted):
We can use sets in refinements ...
Let's consider a primitive lookup table:
As for lists, we start by defining a termination measure:
If we have multiple measures on a datatype (here Table
), then the constructor types are refined by all of them, e.g.:
Empty :: { t : Table key val | size t == 0 && keys t == S.empty }
Can you provide a type signature for lookup that makes it check?
Let's test:
Can you provide a refined type signature for delete? Could / should we replace anything with impossible
here?