| Copyright | (c) 2026 The Polytropos Authors |
|---|---|
| License | EUPL-1.2 OR Apache-2.0 |
| Maintainer | nadia.yvette.chambers@ik.me |
| Stability | experimental |
| Portability | non-portable (GHC extensions) |
| Safe Haskell | None |
| Language | GHC2021 |
Polytropos.Cassava
Description
Connects polytropos open records to the standard Haskell CSV library
cassava without intermediate nominal conversions: ToNamedRecord
serializes field names and values straight from the flat
payload, and RepFromNamedRecord looks up each field
label in the CSV header map in \(O(1)\).
Architectural Overview & Rationale
Heterogeneous Row Support
Unlike satellites that require homogeneous rows, polytropos-cassava is
genuinely heterogeneous: the AllToField and AllFromField row constraint walks
require the CSV field classes (ToField / FromField) at each field's own type,
so a row mixing String, Int, and Double columns encodes and decodes directly.
Decoding is strict about field presence (a missing header column fails), but ignores extra columns in the CSV input, matching cassava's own generic behaviour.
When to Use This Module
- When reading or writing CSV files, spreadsheets, or tabular datasets using open records.
Worked Example
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fplugin=Polytropos.Plugin #-}
import qualified Data.Csv as Csv
import Polytropos
import Polytropos.Cassava
type UserRow = Record '["username" ':= String, "score" ':= Int]
row :: UserRow
row = REC { username = "Alice", score = 95 }
-- Encodes to CSV named record:
namedRec = Csv.toNamedRecord rowSynopsis
- class AllToField (r :: Row) where
- encFields :: Proxy r -> [Identity (Any :: Type)] -> [ByteString]
- class AllFromField (r :: Row) where
- decFields :: Proxy r -> NamedRecord -> Parser [Identity (Any :: Type)]
- class FieldNames (r :: Row) where
- fieldNames :: Proxy r -> [String]
Row Constraint Walks
class AllToField (r :: Row) where Source #
Evidence that every field of the row r has a ToField instance
at that field's own type — the heterogeneous analogue of a constraint
fold. The walk applies toField to each erased payload slot exactly where
the row structure guarantees the slot's type.
Instances
| AllToField ('[] :: [Pair Symbol Type]) Source # | |
Defined in Polytropos.Cassava | |
| (KnownSymbol n, ToField a, AllToField r) => AllToField ((n ':= a) ': r) Source # | |
Defined in Polytropos.Cassava | |
class AllFromField (r :: Row) where Source #
Heterogeneous decode walk: every field of the row r has a
FromField instance at its own type, and decFields looks each
column up by name in O(1).
Methods
decFields :: Proxy r -> NamedRecord -> Parser [Identity (Any :: Type)] Source #
Instances
| AllFromField ('[] :: [Pair Symbol Type]) Source # | |
Defined in Polytropos.Cassava | |
| (KnownSymbol n, FromField a, AllFromField r) => AllFromField ((n ':= a) ': r) Source # | |
Defined in Polytropos.Cassava | |
Row Introspection
class FieldNames (r :: Row) where Source #
The field names of a row, in row order, reified from the type level.
Methods
fieldNames :: Proxy r -> [String] Source #
Instances
| FieldNames ('[] :: [Pair Symbol Type]) Source # | |
Defined in Polytropos.Cassava Methods fieldNames :: Proxy ('[] :: [Pair Symbol Type]) -> [String] Source # | |
| (KnownSymbol n, FieldNames r) => FieldNames ((n ':= a) ': r) Source # | |
Defined in Polytropos.Cassava Methods fieldNames :: Proxy ((n ':= a) ': r) -> [String] Source # | |
Orphan instances
| AllFromField r => FromNamedRecord (Record r) Source # | |
Methods parseNamedRecord :: NamedRecord -> Parser (Record r) # | |
| (FieldNames r, AllToField r) => ToNamedRecord (Record r) Source # | |
Methods toNamedRecord :: Record r -> NamedRecord # | |