{-# 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 (..),
    HasNotField (..),
    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 {forall (name :: Symbol) a. Field name a -> a
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 :: Record (Field fieldName a : otherFields) -> a
getField (Field a
x :> Record fields
_) = a
x
  {-# INLINE getField #-}

instance
  HasField fieldName (Record otherFields) a =>
  HasField fieldName (Record (headField ': otherFields)) a
  where
  getField :: Record (headField : otherFields) -> a
getField (Field fieldName a
_ :> Record fields
otherFields) = forall {k} (x :: k) r a. HasField x r a => r -> a
getField @fieldName Record fields
otherFields
  {-# INLINE getField #-}