packages feed

type-level-kv-list-2.0.2.0: src/Data/KVList.hs

{-| This library provide a brief implementation for extensible records.
  It is sensitive to the ordering of key-value items, but has simple type constraints and provides short compile time.
-}

{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ApplicativeDo #-}

module Data.KVList
  (
  -- * Constructors
  -- $setup
    KVList
  , (:=)((:=))
  , (&=)
  , (&=>)
  , kvcons
  , empty
  , singleton
  , ListKey(..)

  -- * Operators
  , get
  , HasKey
  , (&.)
  , (&.?)
  , (&.??)
  )
where

import Prelude

import Data.Functor ((<&>))
import qualified Data.List as List
import Data.Kind (Constraint, Type)
import Data.Typeable (Typeable, typeOf)
import Data.Proxy (Proxy(Proxy))
import GHC.TypeLits (KnownSymbol, Symbol, TypeError, ErrorMessage(Text), symbolVal)
import GHC.OverloadedLabels (IsLabel(..))
import Unsafe.Coerce (unsafeCoerce)


-- Constructors

{- $setup #constructors#
  We can create type level KV list as follows.

  >>> :set -XOverloadedLabels -XTypeOperators
  >>> import Prelude
  >>> import Data.KVList (empty, KVList, (:=)((:=)), (&.), (&=))
  >>> import qualified Data.KVList as KVList
  >>> let sampleList = KVList.empty &= #foo := "str" &= #bar := 34
  >>> type SampleList = KVList '[ "foo" := String, "bar" := Int ]
-}

{-| A value with type level key.
-}
data KVList (kvs :: [Type]) where
  KVNil :: KVList '[]
  KVCons :: (KnownSymbol key) => key := v -> KVList xs -> KVList ((key := v) ': xs)

instance Eq (KVList '[]) where
  (==) _ _ = True

instance (Eq v, Eq (KVList kvs)) => Eq (KVList ((k := v) ': kvs)  ) where
  (==) (KVCons (_ := v1) next1) (KVCons (_ := v2) next2) = v1 == v2 && next1 == next2

{-| -}
instance ShowFields (KVList kvs) => Show (KVList kvs) where
  show kvs =
    ( List.unwords $
      "(KVList.empty" : showFields kvs
    ) ++ ")"

class ShowFields a where
  showFields :: a -> [String]

instance ShowFields (KVList '[]) where
  showFields _ = []

instance ( ShowFields (KVList kvs)
         , Show v
         ) => ShowFields (KVList ((k := v) ': kvs)) where
  showFields (KVCons (k := v) next) =
    let
      firstLine str =
        List.unwords
          [ "&="
          , "#" <> show k
          , ":="
          , str
          ]
    in
    ( case List.lines $ show v of
        [] -> [ firstLine "" ]
        [a] -> [ firstLine a ]
        as ->
          List.concat
            [ [ firstLine "(" ]
            , as <&> \x -> "  " ++ x
            , [ ")" ]
            ]
    ) ++ showFields next

{-| -}
empty :: KVList '[]
empty = KVNil

{-| -}
(&=) :: (KnownSymbol k, Appended kvs '[k := v] ~ appended) => KVList kvs -> (k := v) -> KVList appended
(&=) kvs kv = append kvs (singleton kv)
{-# INLINE (&=) #-}

infixl 1 &=

{-| Applicative version of '(&=)'.

>>> import Data.KVList ((&=>))
>>> :{
  pure KVList.empty
    &=> #foo := (Just 3)
    &=> #bar := (Just "bar")
:}
Just (KVList.empty &= #foo := 3 &= #bar := "bar")

>>> :{
  pure KVList.empty
    &=> #foo := (Just 3)
    &=> #bar := Nothing
:}
Nothing

-}
(&=>) :: (Applicative f, KnownSymbol k, Appended kvs '[k := v] ~ appended) => f (KVList kvs) -> (k := f v) -> f (KVList appended)
(&=>) fkvs (k := fv) = do
  kvs <- fkvs
  v <- fv
  pure $ (&=) kvs (k := v)
{-# INLINE (&=>) #-}

infixl 1 &=>

{-| -}
kvcons :: (KnownSymbol k) => (k := v) -> KVList kvs -> KVList ((k := v) ': kvs)
kvcons = KVCons

{-| -}
data (key :: Symbol) := (value :: Type) where
  (:=) :: ListKey a -> b -> a := b
infix 2 :=

deriving instance (KnownSymbol key, Show value) => Show (key := value)

{-| -}
type HasKey (key :: Symbol) (kvs :: [Type]) (v :: Type) = HasKey_ key kvs kvs v

type family HasKey_ (key :: Symbol) (kvs :: [Type]) (orig :: [Type]) (v :: Type) :: Constraint where
  HasKey_ key '[] '[] v = TypeError ('Text "The KVList is empty.")
  HasKey_ key '[] orig v = TypeError ('Text "The Key is not in the KVList.")
  HasKey_ key ((key := val) ': _) _ v = (val ~ v)
  HasKey_ key (_ ': kvs) orig v = HasKey_ key kvs orig v

{-| -}
type family Appended kvs1 kv2 :: [Type] where
  Appended '[] kv2 = kv2
  Appended (kv ': kvs) kv2 =
    kv ': Appended kvs kv2

{-| -}
append :: (Appended kvs1 kvs2 ~ appended) => KVList kvs1 -> KVList kvs2 -> KVList appended
append KVNil kvs2 = kvs2
append (KVCons kv kvs) kvs2 = KVCons kv (append kvs kvs2)


{-| -}
singleton :: (KnownSymbol k) => (k := v) -> KVList '[ k := v ]
singleton kv = KVCons kv KVNil


{-| -}
get :: (KnownSymbol key, HasKey key kvs v) => ListKey key -> KVList kvs -> v
get p kvs = get_ p kvs kvs

get_ :: (KnownSymbol key, HasKey key orig v) => ListKey key -> KVList kvs -> KVList orig -> v
get_ _ KVNil KVNil = error "Unreachable: The KVList is empty."
get_ _ KVNil _ = error "Unreachable: The Key is not in the KVList."
get_ p (KVCons (k := v) kvs) orig =
  if typeOf p == typeOf k then
    unsafeCoerce v
  else
    get_ p kvs orig


{-| -}
(&.) :: (KnownSymbol key, HasKey key kvs v) => KVList kvs -> ListKey key -> v
(&.) kvs k = get k kvs
infixl 9 &.

{-| Helper operator for optional chain.

@
(&.?) mkvs k = fmap (&. k) mkvs
@

>>> import Data.KVList ((&.?))
>>> :{
  ( KVList.empty
    &= #foo := Just
      (KVList.empty
        &= #bar := "bar"
      )
  ) &. #foo &.? #bar
:}
Just "bar"

-}
(&.?) :: (KnownSymbol key, HasKey key kvs v, Functor f) => f (KVList kvs) -> ListKey key -> f v
(&.?) mkvs k = fmap (&. k) mkvs
infixl 9 &.?

{-| Helper operator for optional chain.

@
(&.??) mkvs k = (&. k) =<< mkvs
@

>>> import Data.KVList ((&.??))
>>> :{
  ( KVList.empty
    &= #foo := Just
      (KVList.empty
        &= #bar := Just "bar"
      )
  ) &. #foo &.?? #bar
:}
Just "bar"

-}
(&.??) :: (KnownSymbol key, HasKey key kvs (m v), Monad m) => m (KVList kvs) -> ListKey key -> m v
(&.??) mkvs k = (&. k) =<< mkvs
infixl 9 &.??

{-| 'ListKey' is just a proxy, but needed to implement a non-orphan 'IsLabel' instance.
In most cases, you only need to create a `ListKey` instance with @OverloadedLabels@, such as `#foo`.
-}
data ListKey (t :: Symbol)
    = ListKey
    deriving (Eq, Typeable)

instance (KnownSymbol t) => Show (ListKey t) where
  show _ = symbolVal (Proxy :: Proxy t)

instance l ~ l' => IsLabel (l :: Symbol) (ListKey l') where
#if MIN_VERSION_base(4, 10, 0)
    fromLabel = ListKey
#else
    fromLabel _ = ListKey
#endif