packages feed

recollections-0.1.1.0: README.md

# recollections

The splicing module needs these extensions and imports:

```haskell
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DerivingVia #-}

import GHC.Generics (Generically1(..))
```

Given an enumeration of things like

```haskell
data Things
  = This
  | That
  | Something
  | Else
  | Entirely
  deriving (Eq, Ord, Show, Enum, Bounded, Generic)
```

We can derive a record that will behave like a homogenous zip-list of static size:

```haskell
mkCollection ''Things

-- data Collection a = Collection
--   { this, that, something, else', entirely :: a
--   }
--   deriving stock (Eq, Show, Generic1, Functor, Foldable, Traversable)
--   deriving Applicative via (Generically1 Collection)

-- Fill the record with its indices
mkIndices ''Things
```

## Nesting

A constructor may wrap the tag type of another collection.
The field then holds that whole collection:

```haskell
import Some qualified

data Tag
  = Something Some.Tag
  | Local
  deriving (Eq, Ord, Show)

mkCollection ''Tag

-- data Collection a = Collection
--   { something :: Some.Collection a
--   , local :: a
--   }
```

The nested collection is looked up in the module defining `Some.Tag`, so the
nested tag and its splices must live together in their own module (and be
exported from it). Every collection generator spliced for the outer tag needs
its counterpart spliced for the nested one. The class instance generators
delegate to the nested instances instead, so `mkDistributive` and
`mkRepresentable` need the same instances for `Some.Collection`.

Stock `Enum` and `Bounded` can't be derived for such tags, so there are
generators that number the nested tags in place:

```haskell
mkBounded ''Tag
mkEnum ''Tag -- needs Bounded Tag, plus Enum and Bounded Some.Tag

-- [minBound .. maxBound] == toList indices
```

## Kmettoverse

With `adjunctions` and `distributive` in the dependencies of the splicing package
(`recollections` itself doesn't depend on them), the classes can be instantiated directly:

```haskell
{-# LANGUAGE TypeFamilies #-}

import Data.Distributive (Distributive(..))
import Data.Functor.Rep (Representable(..))

mkDistributive ''Things
mkRepresentable ''Things

-- instance Distributive Collection where ...
-- instance Representable Collection where
--   type Rep Collection = Things
--   ...
```

`Representable` requires the `Distributive` instance, so both splices are needed.

The classes are looked up in the splicing module scope, by their plain or module-qualified names.
When the modules are imported under an alias, pass the class names explicitly:

```haskell
import qualified Data.Distributive as D
import qualified Data.Functor.Rep as R

mkDistributiveFor ''D.Distributive ''Things
mkRepresentableFor ''R.Representable ''Things
```

## Kmettoverse-lite

Without the extra dependencies, the methods can be generated as top-level functions instead.
Those names clash with the class imports above.

```haskell
-- Distributive
mkDistribute ''Things
-- collect is `distribute . fmap f`

-- Representable
mkIndex ''Things
mkTabulate ''Things
```

## VS

- Vanilla monomorphic records: don't have functor/foldable/traversable/applicative goodies.
- Hand-rolling the functor: be my guest, hand-roll the field enum too, while you're at it.
- `Map Things a`: All fields are mandatory, all keys are known -> lookups are total and O(1). The optional fields are recoverable, with `Collection (Maybe a)`.
- `Things -> a`: The collection can be stored and loaded.

## Gotchas

All the constructors of the tag type must be nullary or wrap a single nested
tag type — anything else is a compile-time error, since a partial `Collection`
would make `index` partial too.

The generated names (`Collection`, `indices`, `distribute`, `index`,
`tabulate`) are fixed, so a tag named e.g. `Index` will clash with them.
For the same reason, a module with nested collections must be imported
qualified, or its generated names become ambiguous with the outer ones.