packages feed

core-data-0.2.1.9: lib/Core/Data/Structures.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

-- |
-- Convenience wrappers around dictionary and collection types and tools
-- facilitating conversion between them and various map and set types in
-- common use in the Haskell ecosystem.
module Core.Data.Structures
  ( -- * Map type
    Map,
    emptyMap,
    singletonMap,
    insertKeyValue,
    containsKey,
    lookupKeyValue,

    -- * Conversions
    Dictionary (K, V, fromMap, intoMap),

    -- * Set type
    Set,
    emptySet,
    singletonSet,
    insertElement,
    containsElement,

    -- * Conversions
    Collection (E, fromSet, intoSet),

    -- * Internals
    Key,
    unMap,
    unSet,
  )
where

import Core.Text.Bytes (Bytes)
import Core.Text.Rope (Rope)
import qualified Data.ByteString as B (ByteString)
import qualified Data.HashMap.Strict as HashMap
import qualified Data.HashSet as HashSet
import Data.Hashable (Hashable)
import qualified Data.Map.Strict as OrdMap
import qualified Data.Set as OrdSet
import qualified Data.Text as T (Text)
import qualified Data.Text.Lazy as U (Text)
import qualified GHC.Exts as Exts (IsList (..))

-- Naming convention used throughout this file is (Thing u) where u is the
-- underlying structure [from unordered-containers] wrapped in the Thing
-- newtype. Leaves p for our Map and s for our Set in tests.

-- |
-- A mapping from keys to values.
--
-- The keys in a map needs to be an instance of the 'Key' typeclass.
-- Instances are already provided for many common element types.
--
-- 'Map' implements 'Foldable', 'Monoid', etc so many common operations such
-- as 'foldr' to reduce the structure with a right fold, 'length' to get the
-- number of key/value pairs in the dictionary, 'null' to test whether the
-- map is empty, and ('<>') to join two maps together are available.
--
-- To convert to other dictionary types see 'fromMap' below.
--
-- (this is a thin wrapper around __unordered-containers__'s
-- 'Data.HashMap.Strict.HashMap', but if you use the conversion functions to
-- extract the key/value pairs in a list the list will be ordered according to
-- the keys' 'Ord' instance)
newtype Map κ ν = Map (HashMap.HashMap κ ν)
  deriving (Show, Eq)

unMap :: Map κ ν -> HashMap.HashMap κ ν
unMap (Map u) = u
{-# INLINE unMap #-}

-- |
-- Types that can be used as keys in dictionaries or elements in collections.
--
-- To be an instance of 'Key' a type must implement both 'Hashable' and 'Ord'.
-- This requirement means we can subsequently offer easy conversion
-- between different the dictionary and collection types you might encounter
-- when interacting with other libraries.
--
-- Instances for this library's 'Rope' and 'Bytes' are provided here, along
-- with many other common types.
class (Hashable κ, Ord κ) => Key κ

instance Key String

instance Key Rope

instance Key Bytes

instance Key T.Text

instance Key U.Text

instance Key Char

instance Key Int

instance Key B.ByteString

instance Foldable (Map κ) where
  foldr f start (Map u) = HashMap.foldr f start u
  null (Map u) = HashMap.null u
  length (Map u) = HashMap.size u

-- |
-- A dictionary with no key/value mappings.
emptyMap :: Map κ ν
emptyMap = Map (HashMap.empty)

-- |
-- Construct a dictionary with only a single key/value pair.
singletonMap :: Key κ => κ -> ν -> Map κ ν
singletonMap k v = Map (HashMap.singleton k v)

-- |
-- Insert a key/value pair into the dictionary. If the key is already present
-- in the dictionary, the old value will be discarded and replaced with the
-- value supplied here.
insertKeyValue :: Key κ => κ -> ν -> Map κ ν -> Map κ ν
insertKeyValue k v (Map u) = Map (HashMap.insert k v u)

-- |
-- If the dictionary contains the specified key, return the value associated
-- with that key.
lookupKeyValue :: Key κ => κ -> Map κ ν -> Maybe ν
lookupKeyValue k (Map u) = HashMap.lookup k u

-- |
-- Does the dictionary contain the specified key?
containsKey :: Key κ => κ -> Map κ ν -> Bool
containsKey k (Map u) = HashMap.member k u

-- |
instance Key κ => Semigroup (Map κ ν) where
  (<>) (Map u1) (Map u2) = Map (HashMap.union u1 u2)

instance Key κ => Monoid (Map κ ν) where
  mempty = emptyMap
  mappend = (<>)

instance Key κ => Exts.IsList (Map κ ν) where
  type Item (Map κ ν) = (κ, ν)
  fromList pairs = Map (HashMap.fromList pairs)
  toList (Map u) = HashMap.toList u

-- |
-- Types that represent key/value pairs that can be converted to 'Map's.
-- Haskell's ecosystem has several such. This typeclass provides an adaptor to
-- get between them. It also allows you to serialize out to an association
-- list.
--
-- For example, to convert a 'Map' to an \"association list\" of key/value
-- pairs, use 'fromMap':
--
-- @
--     answers :: 'Map' 'Rope' 'Int'
--     answers = 'singletonMap' \"Life, The Universe, and Everything\" 42
--
--     list :: [('Rope','Int')]
--     list = 'fromMap' answers
-- @
--
-- Instances are provided for __containers__'s 'Data.Map.Strict.Map' and
-- __unordered-containers__'s 'Data.HashMap.Strict.HashMap' in addition to the
-- instance for @[(κ,ν)]@ lists shown above.

--
-- Getting an instance for [(κ,ν)] was very difficult. The approach
-- implemented below was suggested by Xia Li-yao, @Lysxia was to use
-- type families.
--
-- >   "Maybe you can change your type class to be indexed by the fully
-- >   applied dictionary type, instead of a type constructor * -> * -> *"
--
-- https://stackoverflow.com/questions/53554687/list-instances-for-higher-kinded-types/53556313
--
-- Many thanks for an elegant solution to the problem.
--
class Dictionary α where
  type K α :: *
  type V α :: *
  fromMap :: Map (K α) (V α) -> α
  intoMap :: α -> Map (K α) (V α)

instance Key κ => Dictionary (Map κ ν) where
  type K (Map κ ν) = κ
  type V (Map κ ν) = ν
  fromMap = id
  intoMap = id

-- | from "Data.HashMap.Strict" (and .Lazy)
instance Key κ => Dictionary (HashMap.HashMap κ ν) where
  type K (HashMap.HashMap κ ν) = κ
  type V (HashMap.HashMap κ ν) = ν
  fromMap (Map u) = u
  intoMap u = Map u

-- | from "Data.Map.Strict" (and .Lazy)
instance Key κ => Dictionary (OrdMap.Map κ ν) where
  type K (OrdMap.Map κ ν) = κ
  type V (OrdMap.Map κ ν) = ν
  fromMap (Map u) = HashMap.foldrWithKey OrdMap.insert OrdMap.empty u
  intoMap o = Map (OrdMap.foldrWithKey HashMap.insert HashMap.empty o)

instance Key κ => Dictionary [(κ, ν)] where
  type K [(κ, ν)] = κ
  type V [(κ, ν)] = ν
  fromMap (Map u) = OrdMap.toList (HashMap.foldrWithKey OrdMap.insert OrdMap.empty u)
  intoMap kvs = Map (HashMap.fromList kvs)

-- |
-- A set of unique elements.
--
-- The element type needs to be an instance of the same 'Key' typeclass that
-- is used for keys in the 'Map' type above. Instances are already provided
-- for many common element types.
--
-- 'Set' implements 'Foldable', 'Monoid', etc so many common operations such
-- as 'foldr' to walk the elements and reduce them, 'length' to return the
-- size of the collection, 'null' to test whether is empty, and ('<>') to take
-- the union of two sets are available.
--
-- To convert to other collection types see 'fromSet' below.
--
-- (this is a thin wrapper around __unordered-containers__'s
-- 'Data.HashSet.HashSet', but if you use the conversion functions to extract
-- a list the list will be ordered according to the elements' 'Ord' instance)
newtype Set ε = Set (HashSet.HashSet ε)
  deriving (Show, Eq)

unSet :: Set ε -> HashSet.HashSet ε
unSet (Set u) = u
{-# INLINE unSet #-}

instance Foldable Set where
  foldr f start (Set u) = HashSet.foldr f start u
  null (Set u) = HashSet.null u
  length (Set u) = HashSet.size u

instance Key ε => Semigroup (Set ε) where
  (<>) (Set u1) (Set u2) = Set (HashSet.union u1 u2)

instance Key ε => Monoid (Set ε) where
  mempty = emptySet
  mappend = (<>)

-- |
-- An empty collection. This is used for example as an inital value when
-- building up a 'Set' using a fold.
emptySet :: Key ε => Set ε
emptySet = Set (HashSet.empty)

-- |
-- Construct a collection comprising only the supplied element.
singletonSet :: Key ε => ε -> Set ε
singletonSet e = Set (HashSet.singleton e)

-- |
-- Insert a new element into the collection. Since the 'Set' type does not
-- allow duplicates, inserting an element already in the collection has no
-- effect.
insertElement :: Key ε => ε -> Set ε -> Set ε
insertElement e (Set u) = Set (HashSet.insert e u)

-- |
-- Does the collection contain the specified element?
containsElement :: Key ε => ε -> Set ε -> Bool
containsElement e (Set u) = HashSet.member e u

-- |
-- Types that represent collections of elements that can be converted to
-- 'Set's.  Haskell's ecosystem has several such. This typeclass provides an
-- adaptor to convert between them.
--
-- This typeclass also provides a mechanism to serialize a 'Set' out to a
-- Haskell list. The list will be ordered according to the 'Ord' instance of
-- the element type.
--
-- Instances are provided for __containers__'s 'Data.Set.Set' and
-- __unordered-containers__'s 'Data.HashSet.HashSet' in addition to the
-- instance for @[ε]@ lists described above.
class Collection α where
  type E α :: *
  fromSet :: Set (E α) -> α
  intoSet :: α -> Set (E α)

instance Key ε => Collection (Set ε) where
  type E (Set ε) = ε
  fromSet = id
  intoSet = id

-- | from "Data.HashSet"
instance Key ε => Collection (HashSet.HashSet ε) where
  type E (HashSet.HashSet ε) = ε
  fromSet (Set u) = u
  intoSet u = Set u

-- | from "Data.Set"
instance Key ε => Collection (OrdSet.Set ε) where
  type E (OrdSet.Set ε) = ε
  fromSet (Set u) = HashSet.foldr OrdSet.insert OrdSet.empty u
  intoSet u = Set (OrdSet.foldr HashSet.insert HashSet.empty u)

instance Key ε => Collection [ε] where
  type E [ε] = ε
  fromSet (Set u) = OrdSet.toList (HashSet.foldr OrdSet.insert OrdSet.empty u)
  intoSet es = Set (HashSet.fromList es)