Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 46 additions & 14 deletions STANDARDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,14 @@ Exceptions are granted when:
modules); 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:

```haskell
import qualified Foo.Bar as Baz
import qualified Foo.Quux as Baz
```

### Justification

Explicit export lists are an immediate, clear and obvious indication of what
Expand Down Expand Up @@ -298,12 +306,16 @@ qualified, is good practice, and saves on a lot of prefixing.

## Plutus module import naming conventions

In addition to the general module import rules, we should 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.
In 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.

Some other exceptions to this are allowed where it may be more convenient to avoid longer qualified names.
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:

Expand All @@ -321,11 +333,11 @@ In some cases it may be justified to use a shortened module name:
import Plutus.V1.Ledger.AddressMap qualified as AddrMap
```

Modules under `PlutusTx` that are extensions to `PlutusTx.Prelude` MAY be imported unqualified when it is reasonable to do so.
Modules 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:
The `Plutus.V1.Ledger.Api` module SHOULD be avoided in favour of more
specific modules where possible. For example, we should avoid:

```haskell
import Plutus.V1.Ledger.Api qualified as Ledger (ValidatorHash)
Expand All @@ -339,10 +351,9 @@ import Plutus.V1.Ledger.Scripts qualified as Scripts (ValidatorHash)

### Justification

The Plutus API modules can be confusing, with numerous modules involved, many exporting the same items.

Consistent qualified names help ease this problem, and decrease ambiguity about where imported items come from.

The Plutus API modules can be confusing, with numerous modules involved, many
exporting the same items. Consistent qualified names help ease this problem,
and decrease ambiguity about where imported items come from.

## LANGUAGE pragmata

Expand Down Expand Up @@ -610,7 +621,7 @@ anyway. The only reason to derive either of these is for compatibility with
legacy libraries, which we don't have any of, and the number of which shrinks
every year. If we're using this extension at all, it's probably a mistake.

``Foldable`` is possibly the most widely-used, lawless type class. Its only laws
``Foldable`` is possibly the most widely-used lawless type class. Its only laws
are about self-consistency (such as agreement between ``foldMap`` and
``foldr``), but unlike something like ``Functor``, ``Foldable`` doesn't have any
laws specifying its behaviour outside of 'it compiles'. As a result, even if we
Expand Down Expand Up @@ -820,6 +831,8 @@ data Foo (a :: Type) = Bar | Baz [a]
quux :: forall (m :: Type) . (Monoid m) => [m] -> m -> m
```

`where`-bindings MUST have type signatures.

### Justification

Haskell lists are a large example of the legacy of the language: they (in the
Expand All @@ -833,7 +846,8 @@ fusion optimizations), from the point of view of field values, they are a poor
choice from both an efficiency perspective, both in theory _and_ in practice.
For almost all cases where you would want a list field value, a ``Vector`` field
value is more appropriate, and in almost all others, some other structure (such
as a ``Map``) is even better.
as a ``Map``) is even better. We make a named exception for on-chain code, as no
alternatives presently exist.

Partial functions are runtime bombs waiting to explode. The number of times the
'impossible' happened, especially in production code, is significant in our
Expand Down Expand Up @@ -897,6 +911,24 @@ as well as ensuring that we don't make any errors ourselves. This, together with
explicit foralls, essentially bring the same practices to the kind level as the
Haskell community already considers to be good at the type level.

`where` bindings are quite common in idiomatic Haskell, and quite often contain
non-trivial logic. They're also a common refactoring, and 'hole-driven
development' tool, where you create a hole to be filled with a `where`-bound
definition. Even in these cases, having an explicit signature on
`where`-bindings helps: during development, you can use typed holes inside the
`where`-binding with useful information (absent a signature, you'll get
nothing), and it makes the code much easier to understand, especially if the
`where`-binding is complex. It's also advantageous when 'promoting'
`where`-binds to full top-level definitions, as the signature is already there.
Since we need to do considerable type-level programming as part of Plutus, this
becomes even more important, as GHC's type inference algorithm can often fail in
those cases on `where`-bindings, which will sometimes fail to derive, giving a
very strange error message, which would need a signature to solve anyway. By
making this practice proactive, we are decreasing confusion, as well as
increasing readability. While in theory, this standard should extend to
`let`-bindings as well, these are much rarer, and can be given signatures with
`::` if `ScopedTypeVariables` is on (which it is for us by default) if needed.

# Design practices

## Parse, don't validate
Expand Down