This is a shortened version of the style guide, spelling out the rules, but not the motivations.
The words MUST, SHOULD, MUST NOT, SHOULD NOT and MAY are defined as per [RFC 2119][rfc-2119].
The following warnings MUST be enabled for all builds of any project, or any
project component, in the ghc-options of the Cabal file:
-Wall-Wcompat-Wincomplete-uni-patterns-Wincomplete-record-updates-Wredundant-constraints-Wmissing-export-lists-Wmissing-deriving-strategies-Werror
Additionally, -Wredundant-constraints SHOULD be enabled for all builds of
any project, in the ghc-options of the Cabal file. Exceptions are allowed
when the additional constraints are designed to ensure safety, rather than due
to reliance on any method. If this warning is to be disabled, it MUST be
disabled in the narrowest possible scope; ideally, this SHOULD be a single
module.
Every source file MUST be free of warnings as produced by [HLint][hlint], using
the settings described in .hlint.yaml. A copy of such a file is provided in
this repository.
Every source file MUST be formatted according to [Fourmolu][fourmolu], with the following settings (as per its settings file):
indentation: 2comma-style: leadingrecord-brace-space: trueindent-wheres: truediff-friendly-import-export: truerespectful: truehaddock-style: multi-linenewlines-between-decls: 1
A copy of a configuration file with these settings is provided in this repository.
Each source code line MUST be at most 80 characters wide.
camelCase MUST be used for all non-type, non-data-constructor names; otherwise,
TitleCase MUST be used. Acronyms used as part of a naming identifier (such as
'JSON', 'API', etc) SHOULD be downcased; thus repairJson and
fromHttpService are correct. Exceptions are allowed for external libraries
(Aeson's parseJSON for example).
All modules MUST use the following conventions for imports:
import Foo (Baz (Quux, quux), Bar, frob)import qualified Bar.Foo as Foo
If ImportQualifiedPost is enabled, the following form MAY also be used:
import Bar.Foo qualified as Foo
Some specific examples cases follow. Type class methods SHOULD be imported alongside their class:
import Control.Applicative (Alternative ((<|>)))An exception is given when only the method is required:
import Control.Applicative (empty)Record fields MUST be imported alongside their record:
import Data.Monoid (Endo (appEndo))Data types from modules imported qualified SHOULD be imported unqualified by themselves:
import Data.Vector (Vector)
import qualified Data.Vector as Vector
An exception is given if such an import would cause a name clash:
-- no way to import both of these without clashing on the Vector type name
import qualified Data.Vector as Basic
import qualified Data.Vector.Storable as Storable
-- We now use Basic.Vector to refer to the Vector in Data.Vector, and
-- Storable.Vector otherwise.
We also permit an exception to use a 'hiding import' to replace part of the
``Prelude``:
```haskell
-- replace the String-based readFile with a Text-based one
import Prelude hiding (readFile)
import Data.Text.IO (readFile)Data constructors MUST be imported individually. For example, given the following data type declaration:
module Quux where
data Foo = Bar Int | BazIts corresponding import should be:
import Quux (Foo, Bar, Baz)Qualified imports SHOULD use their entire module name (that is, the last component of its hierarchical name) as the prefix. For example:
import qualified Data.Vector as VectorExceptions are granted when:
- The import would cause a name clash anyway (such as different
vectormodules); or - We have to import a data type qualified as well.
Qualified imports of multiple modules MUST NOT be imported under the same name. Thus, the following is wrong:
-- Do not do this!
import qualified Foo.Bar as Baz
import qualified Foo.Quux as BazAll modules MUST have explicit export lists; that is, every module must state what exactly it exports. Export lists SHOULD be separated using Haddock headings:
module Foo.Bar (
-- * Types
Baz,
Quux (Quux),
-- * Construction
mkBaz,
quuxFromBaz,
-- etc
) whereAn exception is granted when the module provides few exported identifiers, or if the module doesn't have a large variety of functionality. In the specific case of modules that exist only to provide instances (for compatibility, for example), the export list MUST be empty.
Exports of data constructors or fields SHOULD be explicit:
-- This is ideal
module Foo.Bar (
Baz(Baz, quux, frob)
) whereAn exception is granted if the number of fields or constructors is large; then, wildcard exports MAY be used:
-- This is fine if Baz has a lot of constructors or fields
module Foo.Bar (
Baz(..)
) whereIn addition to the general module import rules, we follow some conventions on how we import the Plutus API modules, allowing for some flexibility depending on the needs of a particular module.
Modules under the names Plutus, Ledger and Plutus.V1.Ledger SHOULD
be imported qualified with their module name, as per the general module standards.
An exception to this is Plutus.V1.Ledger.Api, where the Ledger name is preferred.
Some other exceptions to this are allowed where it may be more convenient to avoid longer qualified names.
For example:
import Plutus.V1.Ledger.Slot qualified as Slot
import Plutus.V1.Ledger.Tx qualified as Tx
import Plutus.V1.Ledger.Api qualified as Ledger
import Ledger.Oracle qualified as Oracle
import Plutus.Contract qualified as ContractIn some cases it may be justified to use a shortened module name:
import Plutus.V1.Ledger.AddressMap qualified as AddrMapModules under PlutusTx that are extensions to PlutusTx.Prelude MAY be
imported unqualified when it is reasonable to do so.
The Plutus.V1.Ledger.Api module SHOULD be avoided in favour of more
specific modules where possible. For example, we should avoid:
import Plutus.V1.Ledger.Api qualified as LedgerIn favour of:
import Plutus.V1.Ledger.Scripts qualified as ScriptsThe following pragmata MUST be enabled at project level (that is, in the Cabal file):
BangPatternsBinaryLiteralsConstraintKindsDataKindsDeriveFunctorDeriveGenericDeriveTraversableDerivingStrategiesDerivingViaDuplicateRecordFieldsEmptyCaseFlexibleContextsFlexibleInstancesGADTsGeneralizedNewtypeDerivingHexFloatLiteralsInstanceSigsImportQualifiedPostKindSignaturesLambdaCaseMultiParamTypeClassesNoImplicitPreludeNumericUnderscoresOverloadedStringsScopedTypeVariablesStandaloneDerivingTupleSectionsTypeApplicationsTypeOperatorsTypeSynonymInstancesUndecidableInstances
Any other LANGUAGE pragmata MUST be enabled per-file. All language pragmata MUST
be at the top of the source file, written as {-# LANGUAGE PragmaName #-}.
Furthermore, the following pragmata MUST NOT be used, or enabled, anywhere:
DeriveDataTypeableDeriveFoldablePartialTypeSignaturesPostfixOperators
The GHC plugin from record-dot-preprocessor SHOULD be enabled globally.
The PlutusTx.Prelude MUST be used. A 'hiding import' to remove functionality
we want to replace SHOULD be used when necessary. If functionality from the
Prelude in base is needed, it SHOULD be imported qualified. Other
preludes MUST NOT be used.
A project MUST use the [PVP][pvp]. Two, and only two, version numbers MUST be used: a major version and a minor version.
Every publically-exported definition MUST have a Haddock comment, detailing its purpose. If a definition is a function, it SHOULD also have examples of use using [Bird tracks][bird-tracks]. The Haddock for a publically-exported definition SHOULD also provide an explanation of any caveats, complexities of its use, or common issues a user is likely to encounter.
If the code project is a library, these Haddock comments SHOULD carry an
[@since][haddock-since] annotation, stating what version of the library they
were introduced in, or the last version where their functionality or type
signature changed.
For type classes, their laws MUST be documented using a Haddock comment.
All module-level definitions, as well as where-binds, MUST have explicit type
signatures. Type variables MUST have an explicit forall scoping them, and
all type variables MUST have explicit kind signatures. Thus, the following is
wrong:
data Foo a = Bar | Baz [a]
quux :: (Monoid m) => [m] -> m -> mInstead, write it like this:
data Foo (a :: Type) = Bar | Baz [a]
quux :: forall (m :: Type) . (Monoid m) => [m] -> m -> mEach explicit type signature MUST correspond to one definition only. Thus, the following is wrong:
bar :: Int
baz :: Int
(bar, baz) = someOtherFunction someOtherValueInstead, write it like this:
bar :: Int
bar = fst . someOtherFunction $ someOtherValue
baz :: Int
baz = snd . someOtherFunction $ someOtherValueLists SHOULD NOT be field values of types; this extends to Strings. Instead,
Vectors (Texts) SHOULD be used, unless a more appropriate structure exists.
On-chain code, due to a lack of alternatives, is one place lists can be used as
field values of types.
Partial functions MUST NOT be defined. Partial functions SHOULD NOT be used except to ensure that another function is total (and the type system cannot be used to prove it).
Derivations MUST use an explicit [strategy][deriving-strategies]. Thus, the following is wrong:
newtype Foo = Foo (Bar Int)
deriving (Eq, Show, Generic, FromJSON, ToJSON)Instead, write it like this:
newtype Foo = Foo (Bar Int)
deriving stock (Generic)
deriving newtype (Eq, Show)
deriving anyclass (FromJSON, ToJSON)Deriving via SHOULD be preferred to newtype derivation, especially where the underlying type representation could change significantly.
type SHOULD NOT be used. The only acceptable case is abbreviation of large
type-level computations. In particular, type MUST NOT be used to create an
abstraction boundary.
Sum types containing record fields MUST NOT be defined. Thus, the following is not allowed:
data Foo = Bar | Baz { quux :: Int, frob :: (Int, Int) }[Boolean blindness][boolean-blindness] SHOULD NOT be used in the design of any function or API. Returning more meaningful data SHOULD be the preferred choice. The general principle of ['parse, don't validate'][parse-dont-validate] SHOULD guide design and implementation.
Any multi-parameter type class MUST have a functional dependency restricting its relation to a one-to-many at most. In cases of true many-to-many relationships, type classes MUST NOT be used as a solution to the problem.
Any type class not imported from an external dependency MUST have laws. These laws MUST be documented in a Haddock comment on the type class definition, and all instances MUST follow these laws.
Data.Typeable from base SHOULD NOT be used; the only exception is for
interfacing with legacy libraries. Whenever its capabilities are required,
[Type.Reflection][type-reflection] SHOULD be used.