{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-|
Provides "syntax sugar" for constructing @vinyl@ records with
named fields, using GHC's OverloadedLabels extension.
This lets you create records using the following syntax,
clearly associating each field's name with its value:
@
john :: 'V.Rec' 'V.ElField' ['("name", String), '("age", Int)]
john = 'rec_'
#age 30
#name "John Doe"
@
You can also extend an existing record using 'extend_':
@
john' :: 'V.Rec' 'V.ElField' ['("bornAt", String), '("name", String), '("age", Int)]
john' = 'extend_' john
#bornAt "Zurich, Switzerland"
@
-}
module Data.Vinyl.Sugar
(
-- * Record-construction sugar
rec_
, extend_
-- * Extending the syntax sugar
, NamedField(..)
, RecSugarTy(..)
-- * Auxiliary types
, FieldLabel(..)
) where
import Data.Kind
import Foreign.Storable (Storable)
import GHC.OverloadedLabels
import GHC.TypeLits
import qualified Data.Vinyl.ARec as V
import qualified Data.Vinyl.Core as V
import qualified Data.Vinyl.Functor as V
import qualified Data.Vinyl.SRec as V
import qualified Data.Vinyl.TypeLevel as V
-- | A simple proxy for type-level strings.
data FieldLabel (s :: Symbol) = FieldLabel
deriving (Eq, Ord, Enum)
instance s ~ s' => IsLabel s (FieldLabel s') where
fromLabel = FieldLabel
{- |
Specifies how to convert a labeled value into an element of a
vinyl record. The only default instance is for @'Vinyl.ElField'@,
but you can define additional instances for your own interpretation
functors.
-}
class NamedField (s :: Symbol) (r :: k) (f :: k -> Type) (a :: Type)
| r -> s
, r f -> a
where
{- |
Convert a value into a vinyl record element. You can imagine that
this has the following type:
@
toNamedField :: KnownSymbol s => a -> ElField '(s, a)
@
-}
toNamedField :: a -> f r
instance (KnownSymbol s, s ~ s') => NamedField s' '(s, a) V.ElField a where
toNamedField = V.Field
{- |
The workhorse class of this package. Implements a polyvariadic
function that builds up a @'V.Rec'@ and converts it into
the desired output type.
You can enable @rec_@ syntax for your own types by writing additional
@RecSugarTy@ instances, adding a base case to @'rec''@. Example:
@
data MyFunctor r -- elided
newtype MyRec rs = MyRec { unMyRec :: 'V.Rec' MyFunctor rs }
instance 'RecSugarTy' ('V.Rec' MyFunctor rs) (MyRec rs) where
rec' = MyRec
@
-}
class RecSugarTy e o | o -> e where
rec' :: e -> o
instance RecSugarTy (V.Rec f rs) (V.Rec f rs) where
rec' = id
instance (NamedField s r f a, RecSugarTy (V.Rec f (r:rs)) o, lbl ~ FieldLabel s) => RecSugarTy (V.Rec f rs) (lbl -> a -> o) where
rec' rs _lbl a = rec' (toNamedField a V.:& rs)
instance V.NatToInt (V.RLength rs) => RecSugarTy (V.Rec f rs) (V.ARec f rs) where
rec' = V.toARec
instance Storable (V.Rec f rs) => RecSugarTy (V.Rec f rs) (V.SRec f rs) where
rec' = V.toSRec
{- |
Create a record by adding fields to the empty record. This is the
function you will use most of the time.
-}
rec_ :: RecSugarTy (V.Rec f '[]) o => o
rec_ = rec' V.RNil
{- |
Create a record by extending an existing record.
-}
extend_ :: RecSugarTy (V.Rec f rs) o => V.Rec f rs -> o
extend_ = rec'