The Asclepias Developer Guide

This guide is designed for asclepias developers, i.e. folks who contribute the asclepias repository. For the user guide, click here.

These documents describe how to:

  • contribute to asclepias

  • install the Haskell toolchain

  • set up your development environment

To develop and work with asclepias locally, clone the repository:

git clone git@gitlab.novisci.com:nsStat/asclepias.git

Quick start

In this section we provide a quick start guide for getting up and running with asclepias development. In future sections we cover all of these steps in greater detail.

  1. Clone the project: git clone git@gitlab.novisci.com:nsStat/asclepias.git

  2. Install the Haskell toolchain

    1. Install ghcup: curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh

    2. Install GHC: ghcup install ghc

    3. Install Cabal: ghcup install cabal

    4. Install HLS: ghcup install hls

  3. Compile all of the project components (this may take half an hour): cabal build all

  4. Set up your editor for Haskell development using HLS. This is editor-specific.

Installing GHC and Cabal

To compile Haskell libraries and/or applications you will need a suitable compiler. Furthermore, to make the creation, distribution, and building of libraries and applications more convenient several systems and supporting tooling for defining Haskell packages have been created. In particular, the asclepias project uses Glasgow Haskell Compiler (GHC) and the Cabal system for building and packaging Haskell libraries and programs, so consequently it will be helpful to have GHC and Cabal available on your development platform.

Installing ghcup

To install GHC and Cabal we recommend using ghcup, which is a utility for installing various applications in the Haskell toolchain. Please see the linked documentation for instructions on how to launch the ghcup installer.

Once the installer is launched, you will be asked a series of questions. The following is a rough guide to the recommended answers for these questions.

  • Do you want ghcup to automatically add the required PATH variable to your shell startup file? This is optional. Either (i) you should choose the "Yes, prepend" option and the installer will add a line of code to the end of your shell startup file (e.g. $HOME/.bashrc, $HOME/.zshrc, etc.) appropriately modifying the PATH environmental variable, or (ii) you should choose the "No" option and manually add some code to your shell startup file performing an equivalent modification to PATH. In the latter case you should prepend $HOME/.cabal/bin and $HOME/.ghcup/bin to PATH.

  • Do you want to install haskell-language-server (HLS)? Select "Yes".

  • Do you want to install stack? There is no harm in selecting "Yes", but it is not necessary for the asclepias project. So in other words, you can select "No" and download it at a later time if desired.

Once installed use ghcup --help to see a description of all the available commands.

Troubleshooting ghcup: SSL certificate problem

Some users on our team (as of January 2022) have reported difficulties when using ghcup (i.e. when using a command such as ghcup list after installation) where they encounter an error with a message like the following.

curl: (60) SSL certificate problem: self signed certificate in certificate chain

Below we list a few workarounds that we have found for this issue.

  • Update your OpenSSL certificates. If you installed OpenSSL on macOS through homebrew then you can use the command brew postinstall openssl.

  • Upgrade the version of curl. If you run curl --version and it lists an old release date then you may want to install a newer version.

  • Use wget instead of curl when running ghcup by specifying the option --downloader=wget. So for example, you can use the command ghcup --downloader=wget list

Obtaining the ghcup facilities information

Use the command ghcup list to orient yourself with the following information.

  1. What applications and application versions are available for download through ghcup.

  2. What applications have already been downloaded through ghcup.

  3. What the current recommended version of an application is according to ghcup.

  4. What version of a given application is active (see the ghcup application version management section for details on what is meant by "is active").

The first column of the table provided by ghcup list is the least obvious. If a given row has an "✗", then it means that the corresponding version of the application hasn’t been downloaded by ghcup. If it has a "✔", then it means that it has been downloaded by ghcup, and if it has a "✔✔" then it means that it both has been downloaded by ghcup and is the currently active version.

ghcup application version management

The "active version" of a given application according to ghcup is the version that gets run when using a shell command such as ghc without supplying a version number (as in e.g. ghc-9.2.1). To change the currently active version for a particular application, use the ghcup set command. See ghcup set --help for details.

In more detail, ghcup stores various application versions in the $HOME/.ghcup/bin directory which is assumed to be on your shell’s path. Then for example, the ghc file located in that directory is a symbolic link to the active version of GHC according to ghcup (a similar pattern is used for other applications managed by ghcup). So suppose that the active version of GHC was GHC 8.10.7, then running the command ghc in your shell would run GHC 8.10.7 because the ${HOME}/.ghup/bin/ghc file would a symbolic link pointing to GHC 8.10.7.

If you wanted to run a different version of GHC, say GHC 9.2.1, for a particular project without changing the active version, then you could invoke that version directly by running the command ghc-9.2.1 in your shell since this is one of the files in $HOME/.ghcup/bin (assuming that it has previously been downloaded).

Installing GHC and Cabal using ghcup

To install an application you can use a command such as ghcup install ghc 8.10.7. Supplying the version number is optional, and if not supplied then ghcup will install what it considers to be the recommended version. To see which version of a given application that ghcup considers to be recommended, you can use ghcup list as described in the Obtaining the ghcup facilities information section. Use ghcup install --help for documentation.

We recommend installing the recommended version of Cabal according to ghcup, and the version of GHC used in the .gitlab-ci.yml file (see the GHC variable defined therein). So in summary, this means that you can run the following commands. You may already have the necessary versions of GHC and Cabal installed in which case either or both of these commands can be omitted as appropriate (however there is no harm in running them, since ghcup is smart enough to not download an existing application a second time).

ghcup install ghc 8.10.7  # double-check to ensure this is the correct version
ghcup install cabal
If you already have a different version of GHC and/or Cabal installed through ghcup, use the ghcup set command to change the active version.

A note on the Cabal version

As you find your way around the asclepias project, you may notice that in the sub-project Cabal package description files (i.e. the files with filenames ending in .cabal), that the cabal-version is specified as an older version of Cabal (such as version 2.2) than can be downloaded through ghcup. According to Package Field: cabal-version in the Cabal documentation Cabal is mostly backwards compatible so there shouldn’t be any issue with using a newer version.

A note on the GHC version and language extensions

As you find your way around the asclepias project, you may notice that in the sub-project Cabal package description files (i.e. the files with filenames ending in .cabal) that the default-language is specified as Haskell2010, which refers to the Haskell 2010 language report, and is the current definition of the Haskell language (also see Haskell 2010 inn Wikipedia and The Haskell 2010 report in The Haskell Wiki). GHC versions 8.0.2 and later implement the Haskell 2010 language report.

Although the Haskell language definition itself has stayed stable since the Haskell 2010 language report, the GHC compiler supports the adoption of new language features through the use of language extensions, which are opt-in non-standard language features. An effect of the specifying the Haskell 2010 language is that the language extensions listed in the Haskell 2010 Language Extensions in the GHC documentation are enabled by GHC.

Additional language extensions can be specified by various mechanisms in the sub-project Cabal package description files and/or in the source code files themselves as described in the Extensions blog post by Veronika Romashkina.

Compiling asclepias packages

asclepias project organization

The asclepias repository is organized using a multiple project setup. Some of the subdirectories of the repository such as hasklepias-core, hasklepias-main, etc. contain a Cabal package which we sometimes more generically call a "project" or "sub-project".

Note that in the following sections we often refer to a Haskell module, which for the time-being can be thought of as the contents of a Haskell file (but see the The Haskell module system section for a more complete definition).

Cabal packaging overview

Cabal package definition

A Cabal package is defined by the following (see Package Description in the Cabal documentation for full detail).

  • A collection of Haskell files.

  • A package description file with a name of the form package-name.cabal in the package root directory containing metadata about the package.

  • In some circumstances, a file named Setup.hs in the package root directory containing instructions for various setup tasks. This file is only needed when the build-type field in the package description file is specified as Custom (see The Package Field: build-type in the Cabal documentation for more details).

You can locate the various Cabal packages in the repository by using a command such as one of the ones shown below. For this particular project there is also a file hie.yaml (and which is described further in the A note on using HLS in multi-project repositories section) that should (unless it gets out-of-sync) accurately describe the package layout.

# Using `find`
find . -path ./dist-newstyle -prune -o -name '*.cabal'

# Alternatively using `fd`
fd --exclude dist-newstyle '\.cabal$'

Cabal package description file format

The Cabal package description file (i.e. the .cabal file) contains information about the package such as the package name, version, structure, and dependencies. Documentation for this file’s format can be found in Package descriptions in the Cabal documentation.

The description file contains a number of top-level fields called Package Properties and which contain information such as cabal-version, name, version, etc. In addition to these fields there may be an arbitrary number of sections from a fixed number of types that are called Component Types. The section type keywords include library, executable, and test-suite among others. Typically each section is named (with one important exception mentioned below) and contains a number of section-specific field/value pairs describing the given component. The fields within a section may optionally be indented, but each field within the section must have the same indentation.

Currently, there can only be one publicly exposed library in a package, and its name is the same as package name set by global name field. The name argument to the library section must be omitted (c.f. Library in the Cabal documentation).

Some fields expect lists for their specified values. For a given field, the thesevalues rather confusingly take exactly one of three forms: space separated (no commas allowed between elements), comma separated (a comma is required between elements), or optional comma separated (the elements may all either be comma seperated or all not comma separated). All optional comma separated fields must follow the same comma or non-comma style (the non-comma style is recommended). The Field Syntax Reference in the Cabal documentation lists the grammar for some of the fields.

Cabal package description file example

Consider the following abridged version of the hasklepias-main.cabal file. In this example we see the top-level fields (i.e. the package properties) cabal-version, name, and version, and following these fields we see a total of three sections. The first section has a "library" component type, and because its name is omitted (i.e. there is no text to the right of the library keyword) this is taken to be the sole publicly exposed library for the package, and is given the same name as is specified by the name field (i.e. hasklepias-main). The second section has a "test-suite" component type, and is given the name examples. The third section has an "executable" component type, and is given the name exampleApp.

The visibility of a given module in a package is controlled by the other-modules, exposed-modules and main-is fields. Note that every package module must be listed in one of these fields. The meanings of these fields are described below.

  • exposed-modules: a list of modules exposed to users of the package (note that this field is applicable only to libraries). Here "exposed" means that package users are able access the functions and data exported by a given exposed module.

  • other-modules:: a list of modules used by the component but not exposed to users.

  • main-is: the name of the file containing the Main module (this field is applicable only to executables).

The meanings of some of the other section-specific fields shown in the example are listed below.

  • default-language: which definition of the Haskell language to use.

  • hs-source-dirs: a list of the directories in which to search for Haskell modules.

  • build-depends: a list declaring the library dependencies required to build the package component.

  • type: has different meanings for various component types. For the test-suite type having a value of exitcode-stdio-1.0 means that the testing interface is an executable where test failure with a non-zero exit code when run.

cabal-version:  2.2
name:           hasklepias-main
version:        0.24.0
description:    Please see the README on GitHub at <https://github.com/novisci/asclepias#readme>
homepage:       https://github.com/novisci/asclepias/#readme
bug-reports:    https://github.com/novisci/asclepias/issues
author:         Bradley Saul
maintainer:     bsaul@novisci.com
copyright:      NoviSci, Inc
category:       Data Science
synopsis:       embedded DSL for defining epidemiologic cohorts
license:        BSD-3-Clause
license-file:   LICENSE
build-type:     Simple
extra-source-files:
    README.md
    ChangeLog.md

library
  exposed-modules:
      Hasklepias
      Hasklepias.Misc
      Hasklepias.Reexports
      Hasklepias.ReexportsUnsafe
  hs-source-dirs:
      src
  build-depends:
    -- Internal packages
      event-data-theory
    , hasklepias-appBuilder
    , hasklepias-core
    , hasklepias-templates
    , stype
    -- External packages
    , aeson == 1.5.6.0
    , base >=4.14 && <5
    , containers == 0.6.5.1
    , flow >= 2.0.0.0
    , generic-lens >= 2.2.0.0
    , microlens >= 0.4.12.0
    , safe >= 0.3
    , tasty  >= 1.4.1
    , tasty-hunit >= 0.10
    , text == 1.2.4.1
    , time >= 1.11
    , tuple >= 0.3
    , witherable >= 0.4.1
    , witch >= 1.0
  default-language: Haskell2010

Compiling asclepias packages

The cabal build command is used to compile Cabal packages and package components. There are many command-line arguments that can be provided with cabal build, however for the sake of brevity these are not covered here. See cabal build --help and cabal-build in the Cabal documentation for full details.

'cabal build' troubleshooting: LLVM version on an M1 Mac

Building on an M1 Mac

The first time you run cabal build on an M1 Mac, you may get errors like:

<no location info>: error:
    Warning: Couldn't figure out LLVM version!
             Make sure you have installed LLVM between [9 and 13)

LLVM is provided as part of Xcode which ships with macOS, but as of January 2022 team members using M1 Macs have reported problems with compiling Haskell packages using the default version of LLVM. If you get the above error message, then you probably need to install a different build of LLVM, which you can do using e.g. homebrew as shown below. Note that in this example we specify LLVM version 12 as suggested in the thread shown below, however the LLVM versions that are compatible with Haskell compilations may change over time. See Help wanted for LLVM config for Haskell on Mac on Reddit for a more detailed discussion.

brew install llvm@12

You will also want to add a line like the following to the appropriate shell startup file such as ${HOME}/.bashrc, ${HOME}/.zshrc, etc.

export PATH="/opt/homebrew/opt/llvm@12/bin:$PATH"

Compiling all asclepias packages

As previously mentioned, the asclepias repository is organized using a multiple project setup. Some of the subdirectories of the repository such as hasklepias-core, hasklepias-main, etc. contain a Cabal package. The simplest thing to do to get started is to build (i.e. compile) all of the Cabal projects in the repository using the following command.

cabal update
cabal build all
This could take around half-an-hour to complete the first time that you do it (future compilations take significantly less time since GHC will only recompile modules that have changed since the last compilation).

By default, Cabal doesn’t compile the test suite or benchmarking modules when using cabal build. In the following sections we will see ways to compile these components if desired.

Compiling asclepias packages one-at-a-time

Alternatively, you can build the packages one-at-a-time using a command of the following form. This is useful when you are working on a particular package and don’t want to compile everything at once in order to save time.

cabal update
cabal build hasklepias-main
Compiling a package will still cause you to compile all of its dependencies. Even if you limit yourself to a single package, it can still take quite a long time the first time that you do it.

By default Cabal doesn’t compile the test suite or benchmarking modules for a given package so if you want to compile the tests along with the package itself then you can use e.g. the --enable-tests and/or --enable-profiling options.

cabal update
cabal build hasklepias-main --enable-tests --enable-profiling

Compiling asclepias package components

In addition to specifying a package name to compile, the cabal build command allows you to specify finer-grained units of compilation called package components, where the package components correspond to the sections in the Cabal package description file. For example, in the example Cabal package description file example section the package name was hasklepias-main, and the package components were called hasklepias-main (a library), examples (a test-suite), and exampleApp an executable (recall that the hasklepias-main library was implicitly named after the package name).

Typically package components are identified using the form package:component (the available syntax is actually more flexible than the form shown here). So for example, you could use the command hasklepias-main:examples to compile the examples component from the hasklepias-main package.

Additionally you can use one of the forms package:ctype or all:ctype to compile all components of the specified type (i.e. the ctype) for a given package or across all packages, respectively. So for example, you could use the command hasklepias-main:executables to compile any components with an executable component type from the hasklepias-main package (of which there happens to be one component, i.e. the exampleApp component), or the command all:executables to compile any components with an executable component type from any package the asclepias repository.

There are other ways of specifying a component by specifying either a module name or the filepath of a module that belongs to the target component, however we do not cover those approaches here.

cabal update

# Using the `package:component` form
cabal build hasklepias-main:hasklepias-main
cabal build hasklepias-main:examples
cabal build hasklepias-main:exampleApp

# Using the `package:ctype` form
cabal build hasklepias-main:libraries
cabal build hasklepias-main:tests
cabal build hasklepias-main:executables

# Using the `all:ctype` form
cabal build all:libraries
cabal build all:tests
cabal build all:executables

Setting up a development environment

Installing an editor

Haskell development is well-supported by many popular editors such as Visual Studio Code, Sublime Text, vim / Neovim, Atom, Emacs, and others. If you do not have a preexisting preference of editor, then we recommend using Visual Studio Code to get started. Visual Studio Code is easy to set up for Haskell development and is currently the most popular editor overall.

To see installation instructions for a given editor listed above, visit the corresponding provided link. In the case of Emacs, it is fairly common to use an Emacs distribution (basically a collection of packages bundled with base Emacs) to reduce the effort required to set up Emacs such as Spacemacs, Doom Emacs, Emacs Prelude, or Purcell Emacs, among many others.

Installing the Haskell Language Server

The Haskell language server (HLS) implements the Language Server Protocol (LSP) for the Haskell language. HSL can be very useful for development when paired with an editor with support for LSP (such as one of the editors mentioned above) since it provides immediate feedback from the compiler, among other features.

You can use ghcup to install whatever its current recommended version of HLS is.

ghcup install hls
You may already have installed HLS, in which case you can skip this step (however there is no harm in running it).

Configuring your editor to utilize HLS

Please see Configuring your editor in the HLS documentation for instructions on how to configure your editor to utilize HLS.

A note on using HLS in multi-project repositories

Since the asclepias repository has a multiple project layout (i.e. hasklepias-core, hasklepias-main, etc.), it may not be obvious how to set up HLS. For example, should you run one server that serves all of the files across the various projects, or should you run one server per project?

To resolve this issue, the asclepias repository provides a file hie.yaml in the repository root that specifies the HLS configuration for all of the projects in the repository (see the hie-bios documentation for details). As a result of this setup, you can run a single HLS server that will work correctly for all of the projects in the repository. If you are asked by your editor to specify what directory to start HLS in then you can use the repository root directory.

Some editors may automatically detect the hie-bios configuration setup in the repository and just "do the right thing." If you open a Haskell file in your editor and the LSP client seems to be working properly then you are probably good-to-go.

Troubleshooting HLS

If HLS ever stops working, you may need to clear the cache:

rm -rf ~/.cache/hie-bios/dist-asclepias*

The Haskell module system

Gaining familiarity with the Haskell module system is helpful in becoming proficient with Hasklepias. To fully understand how the provided mechanisms work we will need a basic understanding of Haskell modules and how they are used to manage namespacing. The following subsections provide a basic description of the Haskell module system, but for a complete description please see the following documentation.

  • Modules in A Gentle Introduction to Haskell.

  • Modules in the Haskell 2010 Language Report.

Haskell module overview

A Haskell program consists of a collection of modules. The primary purpose of modules is to provide a mechanism for namespacing. A module is defined as a Haskell declaration and is given a name. By convention, exactly one module is included per file with the filename sans suffix exactly matching the module name. So for example, if a module was named Features, then it would be the sole module included in the file Features.hs.

Module names are required to be a sequence of one or more strings beginning with capital letters and separated by dots with no intervening spaces, such as e.g. Features or Cohort.Core. The . separator is provided to allow package authors to indicate a hierarchy, however the language itself considers all modules to belong to a flat namespace (in other words the . separators have no effect on the program). By convention, a module named Cohort.Core would be the sole module declared in the file Cohort/Core.hs.

Haskell module declarations

The standard form of a module declaration is defined by the keyword module followed by the module name, followed by an optional list of entities enclosed in round parentheses to be exported, followed by the where keyword, and followed by the module body. The body consists of 0 or more import declarations, followed by 0 or more top-level declarations. So in the following example the module is named ExampleCohort1, the export list has the single entity exampleCohort1tests, and the body consists of two import declarations followed by two top-level declarations.

-- Module header
module ExampleCohort1
  ( exampleCohort1tests
  ) where

-- Import declarations
import Cohort.Attrition
import Hasklepias

-- Top level declarations
lookback455 :: Integer
lookback455 = 455

Haskell module exports

Each Haskell module can export 0 or more declarations. Declarations include things like data and type declarations, class and instance declarations, type signatures, function definitions, and so on.

Since this section requires some prior knowledge of Haskell to fully understand it we have included two subsections, one which can be read during a first pass, and another which can be read at a later time if desired.

Haskell module exports: first approximation

Suppose the export list looks like the example shown below. Loosely speaking this means the following.

  1. The Status entity is exported. The (..) syntax can be thought of as meaning "all of the components of the entity."

  2. The makeBaselineFromIndex entity is exported. Many types of entities are just a singular thing and thus the (..) syntax is not applicable.

  3. The Cohort.Attrition module is reexported (this implies that Cohort.Attrition is imported somewhere in the module body).

  ( Status(..)
  , makeBaselineFromIndex
  , module Cohort.Attrition
  )

Haskell module exports: in more detail

The full export specification is rather involved so we will not try to cover everything in full detail, but rather try to cover the most common cases. See Export Lists in the Haskell 2010 Language Report for complete documentation.

  • Data types declared using a data or newtype declaration are typically exported by one of the following forms. Suppose we have the declaration data Status = Include | Exclude. Then

    • The statement Status(..) exports the Status data type as well as all of its constructors, which in this case are Include and Exclude.

    • The statement Status exports the Status data type but not its constructors (which makes the data type an abstract data type, i.e. you can’t construct one "by hand").

  • Data types declared using a type declaration are exported using the type name.

  • Values are exported by providing the value name.

  • Classes are typically exported by using the following form. Suppose that we have a declared a class Predicatable with methods (|||) and (&&&). Then the form Predicatable(..) exports the Predicatable class as well as all of its methods, which in this case are (|||) and (&&&). It is also possible to export Predictable without exporting all of its method declarations, but this is usually not very useful.

  • Imported modules can be reexported by using the following form. Suppose that we import the module Cohort.Attrition, then we can reexport the module using the statement Module Cohort.Attrition.

Haskell module imports

Understanding how Haskell module imports function can be helpful in gaining facility with asclepias since it enables you to trace back where various entities are defined or created. At a high level, an import serves to add 0 or more entities to the module top-level scope. A full definition of module imports can be found in Import Declarations in the GHC documentation.

One concept that module imports provide is that of qualified and non-qualified imports (non-qualified imports are usually referred to simply as "imports"). Suppose that we were to specify a qualified import of the ExampleEvents module (see Example 1 below). This would mean that a given entity exported from within ExampleEvents has to be referred to via the ExampleEvents namespace. So if for example ExampleEvents exports an entity exampleEvents1, then the default syntax to refer to this entity would be ExampleEvents.exampleEvents1. There is also a way to specify an alternative name for the namespace such as EE instead of ExampleEvents to make it more convenient to use within the module (see Example 2 below). On the other hand, a non-qualified import would simply place all of the exported entities from ExampleEvents into the top-level scope (see Example 3 below). In that case you can just refer to exampleEvents1 directly.

The module import system also allows you to import a subset of the exported entities from a given module. To do this you can either provide a list of entities to include from the module exports (see Example 4 below), or conversely you can provide a list of entities to remove from the module exports (see Example 5 below).

Some examples of various forms of module imports are shown below. Note that each of these examples would be expected to come from different module declarations.

-- Example 1. All exported entities from `ExampleEvents` are available in the
-- module, but must be referred to through the `ExampleEvents` namespace
import qualified ExampleEvents
-- Example 2. All exported entities from `ExampleEvents` are available in the
-- module, but must be referred to through the `EE` namespace
import qualified ExampleEvents as EE
-- Example 3. All exported entities from `ExampleEvents` are added to the
-- top-level scope
import ExampleEvents
-- Example 4. Add only `exampleEvents1` and `exampleEvents2` to the top-level
-- scope
import ExampleEvents (exampleEvents1, exampleEvents2)
-- Example 5. Add everything but `exampleSubject1` and `exampleSubject2` to the
-- top-level scope
import ExampleEvents hiding (exampleSubject1, exampleSubject2)

Interactive usage of GHC

The GHC compiler provides an interactive environment (i.e. a read–eval–print loop or REPL) called GHCi (the "i" stands for "Interactive"). It can be very helpful to experiment with the REPL while writing Haskell code, much as you would with other programming languages like R or Python. See Using GHCi in the Cabal documentation for the full GHCi documentation.

Starting GHCi in Cabal projects

To run GHCi in a Cabal project you can use the cabal repl command followed by an optional target package or package component (if the component is not specified cabal repl loads the first component in a package). There are many command-line arguments that can be provided with cabal repl, however for the sake of brevity these are not covered here. See cabal repl --help and cabal repl in the Cabal documentation for full details.

The cabal repl command uses the same method of specifying a target package component as cabal build (see the Compiling asclepias packages section for details). The following command will start GHCi and load the modules in the examples component of the hasklepias-main package into the session (see the Loading modules into GHCi section for more detail on what "load" means).

cabal repl hasklepias-main:examples

Loading modules into GHCi

Loosely speaking, loading a module means that the declarations in the module are made known to GHCi. Loading a module is a prerequisite to adding the module data and definitions to the GHCi top-level scope (unless the module is part of a package known to GHCi). To see what modules are loaded in a GHCi session at any given time you can use the command :show modules in the REPL.

When GHCi is invoked through cabal repl all of the modules in the specified package component are loaded into GHCi. If GHCi is invoked using the command cabal repl hasklepias-main:examples, then the modules in the examples component of the hasklepias-main package are loaded into the session. Additionally, if you want to change which modules are loaded during your session then you can use the :load command in the REPL to (i) load 0 or more specified modules and (ii) to forget all of the previously loaded modules. It iss often more convenient to simply close the current GHCi session and start a new session with the modules from a different package component loaded.

The following examples demonstrate how to view and change what modules are currently loaded.

cabal repl hasklepias-main:examples
:show modules

:load ExampleCohort1
:show modules

:load ExampleCohort1 ExampleEvents
:show modules

For more details see the following documentation.

Reloading updated modules in GHCi

When you update the source code for a given module or modules that have already been loaded and you want GHCi to recompile the program, you can use the :reload command. See GHCi commands :reload in the GHC documentation for details.

Managing scope in GHCi

GHCi provides support for fine-grained control over what top-level declaration are available in the session (i.e. what is in scope). The following subsections describe the various mechanisms that can used to modify the scope. See What’s really in scope at the prompt? for full details.

Module import *-form in GHCi

When a given module is imported in GHCi (i.e. added to the current scope) it can be in one of two forms: the usual import form and a so-called *-form. The regular form places the module exports in scope, whereas the *-form places all top-level bindings in the module in scope.

Viewing the current scope in GHCi

Use the command :show imports to list the modules that are currently in scope, and the command :show bindings to list any binding that were declared directly in the REPL.

In the following example, we first start a new GHCi session and define the object fib. The subsequent :show bindings command then reports that the only binding made at the prompt was for lib. Next we use :module command to add several modules into the scope (see the Controlling what is in scope with the ':module' command in GHCi section for a description of :module). Then a subsequent :show imports command provides the output shown below. This can be read as meaning that the exports from the ExampleEvents and ExampleFeatures1 modules are in scope, whereas the entirety of the ExampleCohort1 and the Main modules are in scope (i.e. they are *-form imports).

cabal repl hasklepias-main:examples
fib = 1 : scanl (+) 1 fib
:show bindings
-- fib :: Num a => [a] = _

:module +*ExampleCohort1 ExampleEvents ExampleFeatures1
:show imports
-- :module +*ExampleCohort1
-- import ExampleEvents
-- import ExampleFeatures1
-- :module +*Main -- added automatically

How module loads affect scope in GHCi

When modules are loaded at GHCi startup (e.g. after invoking cabal repl) or through the :load command then a secondary effect of the load is that an automatic import is added to the scope for the most recently loaded target module. The effect of this import is that (i) the data and definitions of the module are made available to the top-level scope in GHCi, and (ii) all other bindings are removed from the top-level scope.

Note that the above effects also occur for the :add and :reload commands. See The effect of :load on what is in scope in the GHC documentation for more details.

Controlling what is in scope with the 'import' comand in GHCi

You can use the usual Haskell import syntax to add a module’s exports (or possibly a subset of them) to the scope. In the following example we sequentially add the exports from ExampleFeatures1 and ExampleFeatures1 to the scope, but note that the effect is cumulative (i.e. each module is successively added to the scope). See Controlling what is in scope with import in the GHC documentation for full details.

cabal repl hasklepias-main:examples
:show imports
-- :module +*Main -- added automatically

import ExampleFeatures1
:show imports
-- import ExampleFeatures1
-- :module +*Main -- added automatically

import ExampleEvents
:show imports
-- import ExampleFeatures1
-- import ExampleEvents
-- :module +*Main -- added automatically

Controlling what is in scope with the ':module' command in GHCi

An alternative to using an import command to modify the scope is to use the :module command. In the following example we see three forms of the :module command: one with a + that adds module declarations to the current scope, one with a - that removes module declarations from the current scope, and one without either a + or a - which replaces the current scope with a new scope. Furthermore, each module that is imported by the :module command can be either a regular import or a *-form input by either omitting or including an * before each module name. See Controlling what is in scope with the :module command in the GHC documentation for full details.

cabal repl hasklepias-main:examples
:show imports
-- :module +*Main -- added automatically

:module + *ExampleCohort1 *ExampleEvents ExampleFeatures1 ExampleFeatures2
:show imports
-- :module +*ExampleCohort1
-- :module +*ExampleEvents
-- import ExampleFeatures1
-- import ExampleFeatures2
-- :module +*Main -- added automatically

:module - ExampleEvents ExampleFeatures2
:show imports
-- :module +*ExampleCohort1
-- import ExampleFeatures1
-- :module +*Main -- added automatically

:module *ExampleCohort1 *ExampleEvents
:show imports
-- :module +*ExampleCohort1
-- :module +*ExampleEvents

Linting and Formatting

The CI process checks that code in the repository is appropriately formatted and linted, using the brittany and hlint tools respectively.

You can install these locally using (e.g.) cabal:

cabal install brittany
cabal install hlint

or nix (e.g. for hlint):

nix-env -iA nixpkgs.hlint

Scripts are provided to format code locally with:

./scripts/format.sh

or linted using:

./scripts/lint.sh

Versioning asclepias

Asclepias tries to follow semantic versioning 2.0.0.

While each component package of asclepias has its own version, the primary version of asclepias is the version in hasklepias-main. That is, when we refer to the "version of asclepias`", we mean the version number in `hasklepias-main.

Starting with version 0.24.0, release branches should be named by the version number starting with v and without the patch number. For example, v0.24 is the release branch for the 0.24 line (0.24.0, 0.24.1, etc).

The antora-playbook.yml file in the noviverse-site repository determines which branches of asclepias are used for the documentation site.
Until asclepias version 1.0 is released, no guarantees of backwards-compatability are made.