Quickstart

The easiest way to see how to use hs-to-coq is to look at the Makefiles in each of the subdirectories in the examples subdirectory of the repository.

Translating a single Haskell file

You can run hs-to-coq on a single Haskell module (called Main.hs) with the following command.

$ stack exec -- hs-to-coq -e hs-to-coq/base/edits Main.hs --iface-dir hs-to-coq/base -o .

Adjust the paths to the edits and base files in the hs-to-coq repository accordingly. This invocation uses the following commandline options.

-e <editfile>

The -e command line argument tells hs-to-coq to use edits from the specified edit file, and can be used multiple times. In almost every case, you’ll want to include -e hs-to-coq/base/edits, the edit file distributed with the base library.

--iface-dir <dir>

The --iface-dir command line argument tells hs-to-coq where to find the interface files for the translated files in the base library. These interface files contain extra information about produced during translation and are needed to translate any modules that use the base libraries.

-o <dir>

The -o argument specifies the output directory for the generated .v files. In this case, it is the current directory.

An example translated in this way is simple. Check out the Makefile in the examples/simple subdirectory to see how hs-to-coq is invoked with these arguments.

Local Edit files

Often, a particular file will require its own set of edits. These edits can be provided with additional uses of the -e <editfile> command line argument.

An example that uses a local edit file is intervals, as described in Joachim Breitner’s blog post.

Any number of edit files may be provided to hs-to-coq.

Additional Coq definitions

Sometimes an hs-to-coq translation requires the addition of Coq definitions to the output. These definitions can be specified via the preamble and midamble arguments.

-p <preamble-file>

Inserts the Coq definitions from the specified file at the beginning of the output.

For example, the rle example uses a preamble to add additional imports to the output.

-m <midamble-file>

Inserts the Coq definitions from the specified file in the output after the type definitions but before any of the translated code or instance declarations.

Only one preamble and one midamble can be provided to hs-to-coq.

Strict vs. permissive translation

What should hs-to-coq do when it can’t translate a definition? By default, it will throw an error and stop translating. But you can make this behavior more permissive if you want.

--strict
-S

The default option: any definition that can’t be translated will stop the whole development process.

--permissive
-P

In permissive mode, hs-to-coq will either attempt to axiomatize or skip failing definitions when possible. This is particularly useful during development.

Proofs

Once you have translated your module with hs-to-coq, you will want to prove stuff about it. However, if your module includes definitions from base, you need to set up a _CoqProject file so that coq can find all of the necessary definitions.

The Makefile in the rle example demonstrates how this file can be constructed automatically. The proofs in this example use a lemma called map_map from the base library.

Translating a multi-file project

Larger examples include containers and transformers.

These examples use a Makefile to translate each module in the library individually, using edit files, preambles and midambles specific to each module. It also includes the addition of manually written Coq files to the library.

For this scale of project, we recommend starting with one of the Makefiles above and editing it to suit your application.

Avoiding base

hs-to-coq is designed to automatically use definitions from the base library. However, it is sometimes possible to translate small examples so that they are self contained and only require definitions from Coq’s standard library.

An example project that takes this approach is: https://github.com/mit-plv/riscv-coq