packages feed

yarl-0.1.0.2: src/Data/Records/Yarl/LinkedList.hs

{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE UndecidableInstances #-}

-- |
-- Module        : Data.Records.Yarl.LinkedList
-- Copyright     : Gautier DI FOLCO
-- License       : ISC
--
-- Maintainer    : Gautier DI FOLCO <gautier.difolco@gmail.com>
-- Stability     : Unstable
-- Portability   : not portable
--
-- Provide a simple record library working with 'HasField'
--
-- Example:
--
-- > import Data.Records.Yarl.LinkedList
-- >
-- > type Person = Record '[Field "name" String, Field "age" Int]
-- >
-- > marvin :: Person
-- > marvin = Field "marvin" :> Field 42 :> RNil
-- >
-- > desc :: Person -> String
-- > desc p = "My name is " <> p.name <> " and I'm " <> show p.age
--
module Data.Records.Yarl.LinkedList
  ( Record (..),
    Field (..),
    HasField (..),
  )
where

import Data.Kind
import GHC.Records
import GHC.TypeLits

-- | Watch for field name duplication
type family HasNotField (target :: Symbol) (names :: [Type]) :: Constraint where
  HasNotField x '[] = ()
  HasNotField x (Field x v ': ys) = TypeError ('Text "Field already declared: " ':<>: 'ShowType x)
  HasNotField x (Field y v ': ys) = HasNotField x ys

-- | Field container
newtype Field (name :: Symbol) a = Field {fieldValue :: a}

-- | Full extensible record
data Record :: [Type] -> Type where
  RNil :: Record '[]
  (:>) :: HasNotField fieldName fields => Field fieldName a -> Record fields -> Record (Field fieldName a : fields)

infixr 5 :>

instance {-# OVERLAPS #-} HasField fieldName (Record (Field fieldName a ': otherFields)) a where
  getField (Field x :> _) = x
  {-# INLINE getField #-}

instance
  HasField fieldName (Record otherFields) a =>
  HasField fieldName (Record (headField ': otherFields)) a
  where
  getField (_ :> otherFields) = getField @fieldName otherFields
  {-# INLINE getField #-}