alunduil / alunduil/collection-json.hs

Parameterize `Collection` over its element type

Ouverte
#138 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub
enhancement question
Langage dominant
Haskell
Étoiles
3
Forks
1
Merge moyen
5 h 53 min
PR mergées (30 j)
63

Description

## Problem

A caller holding a decoded document and wanting domain values writes the
traversal by hand, including failure collection, because `cItems :: [Item]`
(`src/Data/CollectionJSON.hs:64`) fixes the element type.

`FromCollection`/`ToCollection` don't help. They are whole-collection
isomorphisms — `a` *is* an entire `Collection` — and the per-item case is the
common one. `fromCollection :: Collection -> a`
(`src/Data/CollectionJSON.hs:352`) is also total, so an instance for a type
that rejects a malformed document has nowhere to report the rejection.

## Change

Parameterize `Collection` over its element type and derive the mapping
vocabulary:

```haskell
data Collection a = Collection
{ cVersion :: Text
, cHref :: URI
, cLinks :: [Link]
, cItems :: [a]
, cQueries :: [Query]
, cTemplate :: Maybe Template
, cError :: Maybe Error
}
deriving (Eq, Show, Functor, Foldable, Traversable)
```

Decoding produces `Collection Item`. Converting to domain values is then
`traverse`:

```haskell
traverse deserialise :: Collection Item -> Either String (Collection Character)
fmap serialise :: Collection Character -> Collection Item
```

`length`, `null`, `toList`, and `traverse_` come along with the derived
instances. Per-type conversion stays plain functions the caller supplies —
`deserialise :: Item -> Either String a` and `serialise :: a -> Item` — with no
class and no envelope helper to maintain.

## Bundling the conversion pair

The TypeScript analog bundles `serialise`, `deserialise`, and `template` into a
`CollectionJsonRepresentation` record so its collection-level helper can
reach the template. That helper is what `traverse` replaces, and the caller
sets a template by updating the field:

```haskell
(fmap serialise characters) { cTemplate = Just characterTemplate }
```

A bespoke record earns its place only if callers turn out to pass the pair
around as one value often enough to want a name for it. Additive either way, so
it doesn't gate this release.

## Open question: does `Item` need a parameter too?

The earlier sketch on this issue proposed parameterizing both. The `traverse`
signature above needs only `Collection`; `Item` stays monomorphic and
`Collection Item` is the decoded document.

Parameterizing `Item` as well pays off only for mapping over item *data*
(`iData :: [Datum]`), which nothing here asks for. `Collection a` alone is the
smaller break. Settle this before writing code.

## Consequence

Major bump. Every signature naming `Collection` needs `Collection Item`,
including the `FromJSON`/`ToJSON` instances, which gain `FromJSON a`/`ToJSON a`
constraints.

The fate of `FromCollection`/`ToCollection` is a separate call: `traverse`
covers the per-item case they never served, and their whole-collection case
still works over `Collection Item`. #96 closed as not planned; removing them
needs its own decision, not a drive-by.

## References

- Supersedes the deferral this issue previously recorded, and the typeclass-only
framing of #96 and #32 (both closed)
- TypeScript reference: `genshin.dungeon.studio/packages/collection-json/src/collection-json.ts`
(lines 75-79); use-site
`genshin.dungeon.studio/packages/domain/src/representations/collection-json/characters.ts`
(lines 53-57)

Guide de contribution

Ouvrir le guide de contribution

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.