This document provides rules and guidelines for contributing Haskell and Haskell-related content to the Primer project.
Over time, we may distinguish between requirements/rules and strong preferences. For now, consider this guide to be advisory — there are currently areas where even code written by members of Hackworth Ltd doesn't live up to these standards!
Note that this style guide doesn't apply to code written in the Primer language itself; e.g., code written for the Primer prelude is out of scope.
When possible, we use continuous integration (CI) checks to enforce our style guide, so in many cases, deviations from this guide will be flagged automatically. Otherwise, if you make a contribution that deviates from the guide, a project maintainer may ask you to make changes to your code. In some cases, the maintainer may simply make the necessary changes themselves, and then co-commit the joint contribution to the project's main
branch.
Primer is chiefly intended for use by children and young adults from all over the world. Please keep this in mind when writing code, comments, and documentation, and especially so when writing content that students will see and/or interact with.
Once this project reaches 1.0 status, we will enforce some form of semantic versioning. Our current plan of record is to support the Haskell PVP.
However, until we reach that status, we can only commit to making a best-effort attempt to adhere to PVP/SemVer. Expect Hackage releases of this project to lag significantly behind the main
branch until we near the 1.0 release of the project.
- Implicit importing of
Prelude
is disabled project-wide. Do not import it; useForeword
, our custom prelude, instead. - Always use explicit exports in libraries and executables.
- Imports should either be qualified (e.g.,
import Data.Map.Strict qualified as M
), or given as an explicit import list (e.g.,import Foo (a, b)
). - As an exception to the above rule, the contents of
Foreword
can be imported unqualified (i.e.,import Foreword
). - Except for import-for-reexport tricks, every qualified import should be imported under a unique name.
- Explicit imports in tests are encouraged, but optional, as we want to keep the friction of writing & maintaining tests to a minimum.
- When it makes sense to do so, provide useful
deriving
instances for types. We suggest at leastShow
,Generic
, and, when appropriate,Eq
. - Unless there's a good reason not to do so, preserve the identity between a type's
Show
andRead
instances. - Types that need to be serialized to & from the database and/or API will additionally need
FromJSON
andToJSON
instances. You should usederiving (FromJSON, ToJSON) via PrimerJSON ...
to implement these instances; see the Primer.JSON module for details. - Do not use orphan instances in libraries and executables unless absolutely necessary. (Note that there are a few cases where we do purposely use orphan instances in this project.)
- When orphan instances are necessary, prefer placing them in a separate module whose leaf name is
Orphans.hs
.
- We encourage the use of the
DuplicateRecordFields
,OverloadedRecordDot
, and similar GHC extensions to avoid annoying name clashes in record types. - Don't use leading underscores (
_
) in names, unless it's necessary to avoid a name clash. (This guideline applies not only to lenses, but also more generally.)
- We use the
optics
family of lenses, rather thanlens
, mainly for more ergonomic error messages. - We encourage the use of lenses in code, and recommend writing new lenses for our own types, when it makes sense to do so.
- This implementation of Primer is meant to be used as a highly-available online service, so it should never crash unless absolutely necessary. (An example of a rare case where crashing may be acceptable is when a database integrity issue is detected, and continuing to operate might otherwise exacerbate the issue.) Therefore, don't use
error
,undefined
, or similar crashing actions, in libraries or executables. - For similar reasons, don't use partial functions in libraries or executables.
- Crashing and/or partial functions may be used in tests, when it makes sense to do so.
- This implementation of Primer is meant to be used as a service, and more specifically, to run in a container. Therefore, we make extensive use of logging.
- That said, logs should be output with the standard
syslog
severity levels in mind (debugging, informational, emergency, etc.), and should not be more verbose than necessary. - When outputting any logs, use the standard logging framework that we've designed, which will take care of eliding levels, formatting, deciding where to output the logs, etc. See the Primer.Log module for details.
- Never write directly to
stderr
orstdout
, unless something must be shown to the operator prior to logging being available; e.g., during command line parsing.
- All exported types, terms, etc., should include at least a single-line comment that describes their purpose.
- When writing comments, make use of proper Haddock markup.
- Don't leave notices like
BUG:
orTODO:
in comments without a link to a corresponding GitHub Issue, and file a new one, if needed. We use this mechanism to ensure that, once a bug or unimplemented feature is addressed in the tracker, we can locate all the occurrences of it in the code base. - When linking to a GitHub Issue in this repo in a comment, don't use the short-form bare hash (e.g.,
#975
) that GitHub automatically expands when viewed in its web UI. Instead, use a proper URI to link to the issue (e.g.,https://github.com/hackworthltd/primer/issues/975
). For one thing, these proper URI's are useful in text editors and other code browsing interfaces other than GitHub's own. Additionally, if we were to import the source code into a new repo (or even a new hosting service), those short hashes would no longer make any sense.
- When a module uses
Foreword
, it should always lead with that import, followed by a blank line, like so:
import Foreword
...
- Avoid Template Haskell if possible, as it makes cross-compilation more difficult.
- Limit line length to 100 characters.
- Avoid using semicolons (
;
) to string actions together on a single line. - We use Fourmolu to format our code. See the project's
fourmolu.yaml
for our project-wide settings. - We use HLint for linting. Our HLint settings are fairly opinionated; see
.hlint.yaml
. - Code must pass project-specific linting & formatting checks before being committed to the
main
branch. (Occasional exceptions may be made.)
- Start all new Cabal projects with
default-language: GHC2021
. - Frequently-enabled GHC extensions should be considered for use in Cabal's
default-extensions
stanza.
- Code must pass all tests before being committed to the
main
branch. We may occasionally disable a test to work around a known issue with tooling or other externalities. - When adding new functionality, write at least a few unit/property tests for it, unless it's completely trivial. We especially appreciate tests that make use of the facilities in
Hedgehog.Gen
. - We encourage the use of the
HasCallStack
constraint in tests, as it may make failures easier to debug.
- We can't make promises on backward compatibility with GHC releases older than the one we're currently using to develop the project, but we won't break compatibility arbitrarily.
- We will make a best-effort attempt to test all supported GHC versions in CI.
- As a general rule, we would like to avoid CPP-style conditional compilation, except as a last resort.
- We expect the 1.0 release of the project to have a minimum requirement of at least GHC
9.10.1
, as we want to take advantage of that version's support for JavaScript and WebAssembly targets. - We do not plan to support Haskell implementations other than GHC.
- Within reason, please use proper grammar and punctuation in comments, documentation, etc.
- Please use American English spelling for consistency with the majority of open source projects on the Internet. (See this comment for reference.)
The Primer programming language has been designed with the intention of supporting natural languages other than English. However, for the most part, we assume that students who're using Primer will not be interacting directly with the code in this project, and instead will be using a graphical frontend interface, such as the one implemented by the primer-app
project.
We believe that most internationalization (i18n) issues can be deferred to such frontend projects, and don't need to be dealt with in this project. Indeed, the Primer API has been designed with this separation of concerns in mind. Therefore, at this time, we don't have any particular internationalization requirements or guidelines for this project, though this may change in the future as required.
The Primer programming language has been designed with accessibility (a11y) in mind. For example, we believe that our tree-oriented structure editor approach is a good fit for screen reader applications. However, as with internationalization requirements, we believe that most accessibility concerns can be handled in Primer frontend projects, and need not be directly addressed in this project, except where we have made decisions in the API design to accommodate accessibility requirements. In any case, at this time, we don't have any particular accessibility requirements or guidelines for this project, except that all design decisions should continue to be made with accessibility needs in mind.