packages feed

primitive-containers-0.3.1: test/Main.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE UnboxedTuples #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

import Data.Primitive
import Data.Word
import Data.Int

import Control.Applicative (liftA2)
import Control.Monad (forM)
import Data.Bool (bool)
import Data.Continuous.Set.Lifted (Inclusivity(..))
import Data.Dependent.Map.Class (Universally(..),ApplyUniversally(..))
import Data.Exists (EqForeach(..),OrdForeach(..),EqForallPoly(..),OrdForallPoly(..),Sing)
import Data.Exists (FromJSONForeach(..),SemigroupForeach(..))
import Data.Exists (PrimForall(..),ToJSONKeyForall(..),ToJSONKeyFunctionForall(..))
import Data.Exists (ToJSONForeach(..),FromJSONKeyExists(..),Exists(..))
import Data.Exists (ToSing(..),DependentPair(..),ShowForall(..),ShowForeach(..))
import Data.Exists (WitnessedEquality(..),WitnessedOrdering(..),EqForall(..),OrdForall(..))
import Data.Functor.Const (Const(..))
import Data.Kind (Type)
import Data.List.NonEmpty (NonEmpty((:|)))
import Data.Primitive.UnliftedArray (PrimUnlifted)
import Data.Proxy (Proxy(..))
import Data.Semigroup (Semigroup)
import Test.HUnit.Base (assertEqual)
import Test.QuickCheck (Arbitrary,Gen,(===),(==>))
import Test.Tasty (defaultMain,testGroup,TestTree)
import Test.Tasty.HUnit (testCase,(@?=))
import Text.Read (readMaybe)
import Unsafe.Coerce (unsafeCoerce)
import qualified Data.Aeson as AE
import qualified Data.Aeson.Encoding as AEE
import qualified Data.Text as T
import qualified Test.Tasty.QuickCheck as TQC
import qualified Test.QuickCheck as QC
import qualified Test.QuickCheck.Classes as QCC
import qualified Data.Semigroup as SG
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Foldable as F
import qualified GHC.Exts as E
import qualified Test.QuickCheck.Classes.IsList as QCCL

import qualified Data.Set.Unboxed as SU
import qualified Data.Set.Lifted as SL
import qualified Data.Set.Unlifted as SUL
import qualified Data.Map.Lifted.Lifted as MLL
import qualified Data.Map.Unboxed.Lifted as MUL
import qualified Data.Map.Unboxed.Unboxed as MUU
import qualified Data.Diet.Map.Strict.Unboxed.Lifted as DMUL
import qualified Data.Diet.Map.Strict.Lifted.Lifted as DMLL
import qualified Data.Diet.Set.Lifted as DSL
import qualified Data.Continuous.Set.Lifted as CSL
import qualified Data.Diet.Unbounded.Set.Lifted as DUSL
import qualified Data.Dependent.Map.Lifted.Lifted as DPMLL
import qualified Data.Dependent.Map.Unboxed.Lifted as DPMUL
import qualified Data.Map.Subset.Strict.Lifted as MSL
import qualified Data.Map.Interval.DBTSLL as MIDBTS

main :: IO ()
main = defaultMain $ testGroup "Data"
  [ testGroup "Set"
    [ testGroup "Unboxed"
      [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (SU.Set Int16)))
      , lawsToTest (QCC.ordLaws (Proxy :: Proxy (SU.Set Int16)))
      , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (SU.Set Int16)))
      , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (SU.Set Int16)))
      , lawsToTest (QCC.isListLaws (Proxy :: Proxy (SU.Set Int16)))
      , TQC.testProperty "member" (memberProp @Int16 E.fromList SU.member)
      , TQC.testProperty "tripleton" setTripletonProp
      ]
    , testGroup "Lifted"
      [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (SL.Set Integer)))
      , lawsToTest (QCC.ordLaws (Proxy :: Proxy (SL.Set Integer)))
      , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (SL.Set Integer)))
      , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (SL.Set Integer)))
      , lawsToTest (QCC.isListLaws (Proxy :: Proxy (SL.Set Integer)))
      , TQC.testProperty "member" (memberProp @Integer E.fromList SL.member)
      , TQC.testProperty "nonMember" (nonMemberProp E.fromList SL.member)
      , TQC.testProperty "foldr" (QCCL.foldrProp int32 SL.foldr)
      , TQC.testProperty "foldl'" (QCCL.foldlProp int16 SL.foldl')
      , TQC.testProperty "foldr'" (QCCL.foldrProp int32 SL.foldr')
      , TQC.testProperty "foldMap" foldMapSetProp
      , TQC.testProperty "foldMap'" foldMapStrictSetProp
      , TQC.testProperty "difference" differenceProp
      , TQC.testProperty "intersection" intersectionProp
      , TQC.testProperty "traverse_" traverseSetProp
      , TQC.testProperty "itraverse_" itraverseSetProp
      ]
    , testGroup "Unlifted"
      [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (SUL.Set (PrimArray Int16))))
      , lawsToTest (QCC.ordLaws (Proxy :: Proxy (SUL.Set (PrimArray Int16))))
      , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (SUL.Set (PrimArray Int16))))
      , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (SUL.Set (PrimArray Int16))))
      , lawsToTest (QCC.isListLaws (Proxy :: Proxy (SUL.Set (PrimArray Int16))))
      , TQC.testProperty "member" (memberProp @(PrimArray Int16) E.fromList SUL.member)
      ]
    ]
  , testGroup "Map"
    [ testGroup "Unboxed"
      [ testGroup "Unboxed"
        [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (MUU.Map Word32 Int)))
        , lawsToTest (QCC.ordLaws (Proxy :: Proxy (MUU.Map Word32 Int)))
        , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (MUU.Map Word32 Word)))
        , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (MUU.Map Word32 Int)))
        , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (MUU.Map Word32 Int)))
        , lawsToTest (QCC.isListLaws (Proxy :: Proxy (MUU.Map Word32 Int)))
        , TQC.testProperty "lookup" (lookupProp @Word32 @Int E.fromList MUU.lookup)
        , TQC.testProperty "foldlWithKey'" (mapFoldAgreement MUU.foldlWithKey' M.foldlWithKey)
        , TQC.testProperty "foldrWithKey'" (mapFoldAgreement MUU.foldrWithKey' M.foldrWithKey)
        , TQC.testProperty "foldMapWithKey'" (mapFoldMonoidAgreement MUU.foldMapWithKey' M.foldMapWithKey)
        , TQC.testProperty "mapMaybe" mapMaybeProp
        ]
      , testGroup "Lifted"
        [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (MUL.Map Word32 Integer)))
        , lawsToTest (QCC.ordLaws (Proxy :: Proxy (MUL.Map Word32 Integer)))
        , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (MUL.Map Word32 Integer)))
        , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (MUL.Map Word32 Integer)))
        , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (MUL.Map Word32 Integer)))
        , lawsToTest (QCC.isListLaws (Proxy :: Proxy (MUL.Map Word32 Integer)))
        , TQC.testProperty "lookup-empty" lookupEmptyUnboxedLiftedMapProp
        , TQC.testProperty "mapWithKey" mapWithKeyProp
        , TQC.testProperty "appendWithKey" appendWithKeyUnboxedLiftedProp
        ]
      ]
    , testGroup "Lifted"
      [ testGroup "Lifted"
        [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (MLL.Map Integer Integer)))
        , lawsToTest (QCC.ordLaws (Proxy :: Proxy (MLL.Map Integer Integer)))
        , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (MLL.Map Integer Integer)))
        , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (MLL.Map Integer Integer)))
        , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (MLL.Map Integer Integer)))
        , lawsToTest (QCC.isListLaws (Proxy :: Proxy (MLL.Map Integer Integer)))
        , TQC.testProperty "appendWithKey" appendWithKeyLiftedLiftedProp
        ]
      ]
    , testGroup "Interval"
      [ testGroup "DBTS"
        [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (MIDBTS.Map Word8 Integer)))
        , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (MIDBTS.Map Word8 (S.Set Integer))))
        , lawsToTest (QCC.commutativeSemigroupLaws (Proxy :: Proxy (MIDBTS.Map Word8 (S.Set Integer))))
        , lawsToTest (QCC.idempotentSemigroupLaws (Proxy :: Proxy (MIDBTS.Map Word8 (S.Set Integer))))
        , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (MIDBTS.Map Word8 Integer)))
        , lawsToTest (QCC.isListLaws (Proxy :: Proxy (MIDBTS.Map Word8 Integer)))
        , TQC.testProperty "lookup" dbtsIntervalMapLookupProp
        , testGroup "Unit"
          [ testCase "A" $ do
              let s = MIDBTS.singleton 102 (1 :: Word8) (2 :: Word8) (101 :: Integer)
              show s @?= "fromList [(0,0,102),(1,2,101),(3,255,102)]"
          , testCase "B" $ do
              let s = MIDBTS.singleton 102 (2 :: Word8) (2 :: Word8) (101 :: Integer)
              show s @?= "fromList [(0,1,102),(2,2,101),(3,255,102)]"
          , testCase "C" $ do
              let s = MIDBTS.singleton 102 (0 :: Word8) (0 :: Word8) (101 :: Integer)
              show s @?= "fromList [(0,0,101),(1,255,102)]"
          , testCase "D" $ do
              let s = MIDBTS.fromList 102 [(1 :: Word8, 2 :: Word8, 100 :: Integer),(5,7,101)]
              show s @?= "fromList [(0,0,102),(1,2,100),(3,4,102),(5,7,101),(8,255,102)]"
          , testCase "E" $ do
              let s = MIDBTS.fromList 102 [(5,7,101),(1 :: Word8, 2 :: Word8, 100 :: Integer)]
              show s @?= "fromList [(0,0,102),(1,2,100),(3,4,102),(5,7,101),(8,255,102)]"
          ]
        ]
      ]
    ]
  , testGroup "Dependent"
    [ testGroup "Map"
      [ -- testGroup "Lifted"
        -- [ testGroup "Lifted"
        --   [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (DPMLL.Map Key Value)))
        --   , lawsToTest (QCC.ordLaws (Proxy :: Proxy (DPMLL.Map Key Value)))
        --   , lawsToTest (QCC.isListLaws (Proxy :: Proxy (DPMLL.Map Key Value)))
        --   , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (DPMLL.Map Key Value)))
        --   , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (DPMLL.Map Key Value)))
        --   ]
        -- ]
        testGroup "Unboxed"
        [ testGroup "Lifted"
          [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (DPMUL.Map UnboxedKey Value)))
          , lawsToTest (QCC.ordLaws (Proxy :: Proxy (DPMUL.Map UnboxedKey Value)))
          , lawsToTest (QCC.isListLaws (Proxy :: Proxy (DPMUL.Map UnboxedKey Value)))
          , lawsToTest (QCC.jsonLaws (Proxy :: Proxy (DPMUL.Map UnboxedKey Value)))
          , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (DPMUL.Map UnboxedKey Value)))
          , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (DPMUL.Map UnboxedKey Value)))
          ]
        ]
      ]
    ]
  , testGroup "Continuous"
    [ testGroup "Set"
      [ testGroup "Lifted"
        [ testGroup "Unit" 
          [ testCase "A" $ do
              let s = CSL.singleton Nothing (Just (Inclusive,55 :: Integer))
                      <>
                      CSL.singleton (Just (Exclusive,200 :: Integer)) Nothing
                  str = show s
              assertEqual (str ++ " contains 50") (CSL.member 50 s) True
              assertEqual (str ++ " contains 270") (CSL.member 270 s) True
              assertEqual (str ++ " contains 55") (CSL.member 55 s) True
              assertEqual (str ++ " does not contain 200") (CSL.member 200 s) False
              assertEqual (str ++ " does not contain 56") (CSL.member 56 s) False
          , testCase "B" $ do
              let s = CSL.singleton Nothing (Just (Inclusive,14 :: Integer))
                      <>
                      CSL.singleton (Just (Exclusive,14 :: Integer)) Nothing
              s @?= CSL.universe
          , testCase "C" $ do
              let s = CSL.singleton Nothing (Just (Exclusive,14 :: Integer))
                      <>
                      CSL.singleton (Just (Exclusive,14 :: Integer)) Nothing
                  str = show s
              assertEqual (str ++ " does not contain 14") (CSL.member 14 s) False
          ]
        ]
      ]
    ]
  , testGroup "Diet"
    [ testGroup "Unbounded"
      [ testGroup "Set"
        [ testGroup "Lifted"
          [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (DUSL.Set Word8)))
          , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (DUSL.Set Word8)))
          , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (DUSL.Set Word8)))
          ]
        ]
      ]
    , testGroup "Set"
      [ testGroup "Lifted"
        [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (DSL.Set Word16)))
        , lawsToTest (QCC.ordLaws (Proxy :: Proxy (DSL.Set Word16)))
        , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (DSL.Set Word16)))
        , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (DSL.Set Word16)))
        , lawsToTest (QCC.isListLaws (Proxy :: Proxy (DSL.Set Word16)))
        , TQC.testProperty "member" (dietMemberProp @Word8 E.fromList DSL.member)
        , TQC.testProperty "difference" dietSetDifferenceProp
        , TQC.testProperty "intersection" dietSetIntersectionProp
        , TQC.testProperty "negate" dietSetNegateProp
        , TQC.testProperty "aboveInclusive" dietSetAboveProp
        , testGroup "belowInclusive"
          [ TQC.testProperty "basic" dietSetBelowProp
          , TQC.testProperty "lowest" dietSetBelowLowestProp
          , TQC.testProperty "highest" dietSetBelowHighestProp
          ]
        , testGroup "betweenInclusive"
          [ TQC.testProperty "basic" dietSetBetweenProp
          , TQC.testProperty "border" dietSetBetweenBorderProp
          , TQC.testProperty "inside" dietSetBetweenBorderNearProp
          ]
        ]
      ]
    , testGroup "Map"
      [ testGroup "Subset"
        [ testGroup "Lifted"
          [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (MSL.Map Integer (SG.Sum Integer))))
          , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (MSL.Map Integer (SG.First Integer))))
          , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (MSL.Map Integer (SG.Sum Integer))))
          , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (MSL.Map Integer (SG.Sum Integer))))
          , TQC.testProperty "lookup" subsetMapLookupProp
          ]
        ]
      , testGroup "Lifted"
        [ testGroup "Lifted"
          [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (DMLL.Map Word8 Integer)))
          , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (DMLL.Map Word8 Word)))
          , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (DMLL.Map Word8 Int)))
          , lawsToTest (QCC.isListLaws (Proxy :: Proxy (DMLL.Map Word8 Integer)))
          , TQC.testProperty "lookup" (dietLookupPropA @Word8 @Int E.fromList DMLL.lookup)
          , TQC.testProperty "doubleton" dietDoubletonProp
          , TQC.testProperty "valid" dietValidProp
          ]
        ]
      , testGroup "Unboxed"
        [ testGroup "Lifted"
          [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (DMUL.Map Word8 Integer)))
          , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (DMUL.Map Word8 Word)))
          , lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (DMUL.Map Word8 Int)))
          , lawsToTest (QCC.isListLaws (Proxy :: Proxy (DMUL.Map Word8 Integer)))
          , TQC.testProperty "lookup" (dietLookupPropA @Word32 @Int E.fromList DMUL.lookup)
          ]
        ]
      ]
    ]
  ]

int16 :: Proxy Int16
int16 = Proxy

int32 :: Proxy Int32
int32 = Proxy

subsetMapLookupProp :: QC.Property
subsetMapLookupProp = QC.property $ \(xs :: MSL.Map Integer Integer) ->
  let xs' = MSL.toList xs
   in all (\(k,v) -> MSL.lookup k xs == Just v) xs' === True

dietSetDifferenceProp :: QC.Property
dietSetDifferenceProp = QC.property $ \(xs :: DSL.Set Word8) (ys :: DSL.Set Word8) ->
  let xs' = dietSetToSet xs
      ys' = dietSetToSet ys
   in DSL.difference xs ys === DSL.fromList (map (\x -> (x,x)) (S.toList (S.difference xs' ys')))

dietSetIntersectionProp :: QC.Property
dietSetIntersectionProp = QC.property $ \(xs :: DSL.Set Word8) (ys :: DSL.Set Word8) ->
  let xs' = dietSetToSet xs
      ys' = dietSetToSet ys
   in DSL.intersection xs ys === DSL.fromList (map (\x -> (x,x)) (S.toList (S.intersection xs' ys')))

dietSetNegateProp :: QC.Property
dietSetNegateProp = QC.property $ \(xs :: DSL.Set Word8) ->
  let xs' = dietSetToSet xs
      expected = foldMap (\n -> bool (S.singleton n) mempty (S.member n xs')) [minBound..maxBound]
   in DSL.negate xs === mconcat (map (\x -> DSL.singleton x x) (F.toList expected))

dietSetAboveProp :: QC.Property
dietSetAboveProp = QC.property $ \(y :: Word8) (ys :: DSL.Set Word8) ->
  let ys' = dietSetToSet ys
      (_,isMember,c) = S.splitMember y ys'
      r = if isMember then S.insert y c else c
   in DSL.aboveInclusive y ys === DSL.fromList (map (\x -> (x,x)) (S.toList r))

dietSetBelowProp :: QC.Property
dietSetBelowProp = QC.property $ \(y :: Word8) (ys :: DSL.Set Word8) ->
  let ys' = dietSetToSet ys
      (c,isMember,_) = S.splitMember y ys'
      r = if isMember then S.insert y c else c
   in DSL.belowInclusive y ys === DSL.fromList (map (\x -> (x,x)) (S.toList r))

dietSetBelowLowestProp :: QC.Property
dietSetBelowLowestProp = QC.property $ \(ys :: DSL.Set Word8) ->
  let ys' = dietSetToSet ys
   in case S.lookupMin ys' of
        Nothing -> QC.property QC.Discard
        Just y -> 
          let (c,isMember,_) = S.splitMember y ys'
              r = if isMember then S.insert y c else c
           in QC.property (DSL.belowInclusive y ys === DSL.fromList (map (\x -> (x,x)) (S.toList r)))

dietSetBelowHighestProp :: QC.Property
dietSetBelowHighestProp = QC.property $ \(ys :: DSL.Set Word8) ->
  let ys' = dietSetToSet ys
   in case S.lookupMax ys' of
        Nothing -> QC.property QC.Discard
        Just y -> 
          let (c,isMember,_) = S.splitMember y ys'
              r = if isMember then S.insert y c else c
           in QC.property (DSL.belowInclusive y ys === DSL.fromList (map (\x -> (x,x)) (S.toList r)))

dietSetBetweenProp :: QC.Property
dietSetBetweenProp = QC.property $ \(x :: Word8) (y :: Word8) (ys :: DSL.Set Word8) ->
  (x <= y)
  ==> 
  ( let ys' = dietSetToSet ys
        r = S.filter (\e -> e >= x && e <= y) ys'
     in DSL.betweenInclusive x y ys === DSL.fromList (map (\z -> (z,z)) (S.toList r))
  )

dietSetBetweenBorderProp :: QC.Property
dietSetBetweenBorderProp = QC.property $ \(ys :: DSL.Set Word8) ->
  let ys' = dietSetToSet ys
   in case S.lookupMax ys' of
        Nothing -> QC.property QC.Discard
        Just hi -> case S.lookupMin ys' of
          Nothing -> QC.property QC.Discard
          Just lo -> 
            let r = S.filter (\e -> e >= lo && e <= hi) ys'
             in DSL.betweenInclusive lo hi ys === DSL.fromList (map (\z -> (z,z)) (S.toList r))

dietSetBetweenBorderNearProp :: QC.Property
dietSetBetweenBorderNearProp = QC.property $ \(ys :: DSL.Set Word8) ->
  let ys' = dietSetToSet ys
   in ( S.size ys' > 1
        ==>
        ( let hi = pred (S.findMax ys')
              lo = succ (S.findMin ys')
              r = S.filter (\e -> e >= lo && e <= hi) ys'
           in DSL.betweenInclusive lo hi ys === DSL.fromList (map (\z -> (z,z)) (S.toList r))
        )
      )

-- This enumerates all of the element contained by all ranges
-- in the diet set.
dietSetToSet :: (Enum a, Ord a) => DSL.Set a -> S.Set a
dietSetToSet = DSL.foldr
  (\lo hi s -> S.fromList (enumFromTo lo hi) SG.<> s)
  mempty

differenceProp :: QC.Property
differenceProp = QC.property $ \(xs :: S.Set Word8) (ys :: S.Set Word8) ->
  let xs' = SL.fromList (S.toList xs)
      ys' = SL.fromList (S.toList ys)
   in SL.toList (SL.difference xs' ys') === S.toList (S.difference xs ys)

intersectionProp :: QC.Property
intersectionProp = QC.property $ \(xs :: S.Set Word8) (ys :: S.Set Word8) ->
  let xs' = SL.fromList (S.toList xs)
      ys' = SL.fromList (S.toList ys)
   in SL.toList (SL.intersection xs' ys') === S.toList (S.intersection xs ys)

traverseSetProp :: QC.Property
traverseSetProp = QC.property $ \(xs :: S.Set Word8) ->
  let xs' = SL.fromList (S.toList xs)
   in SL.traverse_ (Const . SG.Sum) xs' === F.traverse_ (Const . SG.Sum) xs

foldMapSetProp :: QC.Property
foldMapSetProp = QC.property $ \(xs :: S.Set Word8) ->
  let xs' = SL.fromList (S.toList xs)
   in SL.foldMap SG.Sum xs' === F.foldMap SG.Sum xs

foldMapStrictSetProp :: QC.Property
foldMapStrictSetProp = QC.property $ \(xs :: S.Set Word8) ->
  let xs' = SL.fromList (S.toList xs)
   in SL.foldMap' SG.Sum xs' === F.foldMap SG.Sum xs

mapMaybeProp :: QC.Property
mapMaybeProp = QC.property $ \(xs :: M.Map Word8 Word8) ->
  let xs' = MUU.fromList (M.toList xs)
      func x = if even x then Just (x * x) else Nothing
   in MUU.toList (MUU.mapMaybe func xs') === M.toList (M.mapMaybe func xs)

mapWithKeyProp :: QC.Property
mapWithKeyProp = QC.property $ \(xs :: M.Map Word8 Word8) ->
  let xs' = MUL.fromList (M.toList xs)
      func x y = if even x then y * x else x + 1
   in MUL.toList (MUL.mapWithKey func xs') === M.toList (M.mapWithKey func xs)

appendWithKeyUnboxedLiftedProp :: QC.Property
appendWithKeyUnboxedLiftedProp = QC.property $ \(xs :: M.Map Word8 Word8) ys ->
  let xs' = MUL.fromList (M.toList xs)
      ys' = MUL.fromList (M.toList ys) 
      func k x y = k + 2 * x + 3 * y
   in MUL.toList (MUL.appendWithKey func xs' ys') === M.toList (M.unionWithKey func xs ys)

appendWithKeyLiftedLiftedProp :: QC.Property
appendWithKeyLiftedLiftedProp = QC.property $ \(xs :: M.Map Word8 Word8) ys ->
  let xs' = MLL.fromList (M.toList xs)
      ys' = MLL.fromList (M.toList ys) 
      func k x y = k + 2 * x + 3 * y
   in MLL.toList (MLL.appendWithKey func xs' ys') === M.toList (M.unionWithKey func xs ys)

itraverseSetProp :: QC.Property
itraverseSetProp = QC.property $ \(xs :: S.Set Int) ->
  let xs' = SL.fromList (S.toList xs)
      zs = zip (enumFrom (0 :: Int)) (S.toList xs)
   in SL.itraverse_ (\ix x -> Const (SG.Sum (ix + x))) xs' === F.traverse_ (\(ix,x) -> Const (SG.Sum (ix + x))) zs

mapFoldMonoidAgreement ::
     ((Int -> Int -> [Int]) -> MUU.Map Int Int -> [Int])
  -> ((Int -> Int -> [Int]) -> M.Map Int Int -> [Int])
  -> QC.Property
mapFoldMonoidAgreement foldPrim foldContainer = QC.property $ \(xs :: [(Int,Int)]) ->
  let p = E.fromList xs
      c = E.fromList xs
      func x y = [x + y]
   in foldPrim func p === foldContainer func c

mapFoldAgreement ::
     ((Int -> Int -> Int -> Int) -> Int -> MUU.Map Int Int -> Int)
  -> ((Int -> Int -> Int -> Int) -> Int -> M.Map Int Int -> Int)
  -> QC.Property
mapFoldAgreement foldPrim foldContainer = QC.property $ \(xs :: [(Int,Int)]) ->
  let p = E.fromList xs
      c = E.fromList xs
      -- we just need the function to be non-commutative
      func x y z = y - (2 * x) - (3 * z)
   in foldPrim func 42 p === foldContainer func 42 c

memberProp :: forall a t. (Arbitrary a, Show a) => ([a] -> t a) -> (a -> t a -> Bool) -> QC.Property
memberProp containerFromList containerMember = QC.property $ \(xs :: [a]) ->
  let c = containerFromList xs
   in all (\x -> containerMember x c) xs === True

setTripletonProp :: QC.Property
setTripletonProp = QC.property $ \(a :: Int16) (b :: Int16) (c :: Int16) ->
  SU.tripleton a b c === SU.fromList [a,b,c]

nonMemberProp :: forall t. ([Integer] -> t Integer) -> (Integer -> t Integer -> Bool) -> QC.Property
nonMemberProp containerFromList containerMember = QC.property $ \(xs :: [Integer]) ->
  let c = containerFromList xs
      upper = case xs of
        [] -> 42
        _ : _ -> maximum xs
      lower = case xs of
        [] -> (-42)
        _ : _ -> minimum xs
   in (containerMember (succ upper) c, containerMember (pred lower) c) === (False,False)

lookupProp :: forall k v t. (Arbitrary k, Show k, Ord k, Arbitrary v, Show v, Eq v) => ([(k,v)] -> t k v) -> (k -> t k v -> Maybe v) -> QC.Property
lookupProp containerFromList containerLookup = QC.property $ \(xs :: [(k,v)]) ->
  let ys = M.fromList xs
      c = containerFromList xs
   in all (\(x,_) -> containerLookup x c == M.lookup x ys) xs === True

lookupEmptyUnboxedLiftedMapProp :: QC.Property
lookupEmptyUnboxedLiftedMapProp = QC.property $ \(x :: Word16) ->
  MUL.lookup x (MUL.empty :: MUL.Map Word16 Integer) === Nothing

dietMemberProp :: forall a t. (Arbitrary a, Show a, Ord a, Arbitrary a, Show (t a)) => ([(a,a)] -> t a) -> (a -> t a -> Bool) -> QC.Property
dietMemberProp containerFromList containerLookup = QC.property $ \(xs :: [a]) ->
  let c = containerFromList (map (\a -> (a,a)) xs)
   in QC.counterexample ("original list: " ++ show xs ++ "; diet set: " ++ show c) (all (\x -> containerLookup x c == True) xs === True)

dietLookupPropA :: forall k v t. (Arbitrary k, Show k, Ord k, Arbitrary v, Show v, Eq v, Show (t k v)) => ([(k,k,v)] -> t k v) -> (k -> t k v -> Maybe v) -> QC.Property
dietLookupPropA containerFromList containerLookup = QC.property $ \(xs :: [(k,v)]) ->
  let ys = M.fromList xs
      c = containerFromList (map (\(k,v) -> (k,k,v)) xs)
   in QC.counterexample ("original list: " ++ show xs ++ "; diet map: " ++ show c) (all (\(x,_) -> containerLookup x c == M.lookup x ys) xs === True)

dbtsIntervalMapLookupProp :: QC.Property
dbtsIntervalMapLookupProp = QC.property $ \(xs :: [(Word8,Word8,Integer)]) (k :: Word8) ->
  let ys = MIDBTS.fromList Nothing (fmap (\(lo,hi,r) -> (lo,hi,Just r)) xs)
      expected = fmap (\(_,_,r) -> r) (F.find (\(lo,hi,_) -> lo <= k && k <= hi) xs)
   in expected === MIDBTS.lookup k ys

dietDoubletonProp :: QC.Property
dietDoubletonProp = QC.property $ \(loA :: Word8) (hiA :: Word8) (valA :: Int) (loB :: Word8) (hiB :: Word8) (valB :: Int) ->
  (hiA >= loA && hiB >= loB)
  ==>
  (simpleDoubletonToList loA hiA valA loB hiB valB === E.toList (DMLL.singleton loA hiA valA SG.<> DMLL.singleton loB hiB valB))

dietValidProp :: QC.Property
dietValidProp = QC.property $ \(xs :: DMLL.Map Word8 Int) ->
  True === validDietTriples (E.toList xs)

simpleDoubletonToList :: (Ord k, Enum k, Semigroup v, Eq v) => k -> k -> v -> k -> k -> v -> [(k,k,v)]
simpleDoubletonToList key1A key2A valA key1B key2B valB =
  let loA = min key1A key2A
      hiA = max key1A key2A
      loB = min key1B key2B
      hiB = max key1B key2B
   in deduplicate $ case compare loA loB of
        LT -> case compare hiA loB of
          LT -> [(loA,hiA,valA),(loB,hiB,valB)]
          EQ -> case compare hiA hiB of
            LT -> [(loA,pred loB,valA),(loB,hiA,valA SG.<> valB),(succ hiA,hiB,valB)]
            EQ -> [(loA,pred loB,valA),(loB,hiA,valA SG.<> valB)]
            GT -> error "simpleDoubletonToList: invariant violated"
          GT -> case compare hiA hiB of
            LT -> [(loA,pred loB,valA),(loB,hiA,valA SG.<> valB),(succ hiA,hiB,valB)]
            EQ -> [(loA,pred loB,valA),(loB,hiA,valA SG.<> valB)]
            GT -> [(loA,pred loB,valA),(loB,hiB,valA SG.<> valB),(succ hiB,hiA,valA)]
        EQ -> case compare hiA hiB of
          LT -> [(loA,hiA,valA SG.<> valB),(succ hiA, hiB, valB)]
          GT -> [(loB,hiB,valA SG.<> valB),(succ hiB, hiA, valA)]
          EQ -> [(loA,hiA,valA SG.<> valB)]
        GT -> case compare hiB loA of
          LT -> [(loB,hiB,valB),(loA,hiA,valA)]
          EQ -> case compare hiB hiA of
            LT -> [(loB,pred loA,valB),(loA,hiB,valA SG.<> valB),(succ hiB,hiA,valA)]
            EQ -> [(loB,pred loA,valB),(loA,hiB,valA SG.<> valB)]
            GT -> error "simpleDoubletonToList: invariant violated"
          GT -> case compare hiB hiA of
            LT -> [(loB,pred loA,valB),(loA,hiB,valA SG.<> valB),(succ hiB,hiA,valA)]
            EQ -> [(loB,pred loA,valB),(loA,hiB,valA SG.<> valB)]
            GT -> [(loB,pred loA,valB),(loA,hiA,valA SG.<> valB),(succ hiA,hiB,valB)]

validDietTriples :: (Enum k,Eq k,Eq v) => [(k,k,v)] -> Bool
validDietTriples xs = deduplicate xs == xs

deduplicate :: (Enum k,Eq k, Eq v) => [(k,k,v)] -> [(k,k,v)]
deduplicate [] = []
deduplicate (x : xs) = F.toList (deduplicateNonEmpty (x :| xs))

deduplicateNonEmpty :: (Enum k, Eq k, Eq v) => NonEmpty (k,k,v) -> NonEmpty (k,k,v)
deduplicateNonEmpty ((lo,hi,v) :| xs) = case xs of
  y : ys -> case deduplicateNonEmpty (y :| ys) of
    (lo',hi',v') :| xs' -> if v == v' && pred lo' == hi
      then (lo,hi',v) :| xs'
      else (lo,hi,v) :| ((lo',hi',v') : xs')
  [] -> (lo,hi,v) :| []

lawsToTest :: QCC.Laws -> TestTree
lawsToTest (QCC.Laws name pairs) = testGroup name (map (uncurry TQC.testProperty) pairs)

instance (Arbitrary a, Prim a) => Arbitrary (PrimArray a) where
  arbitrary = fmap E.fromList QC.arbitrary

instance (Arbitrary a, Prim a, Ord a) => Arbitrary (SU.Set a) where
  arbitrary = fmap E.fromList QC.arbitrary

instance (Arbitrary a, PrimUnlifted a, Ord a) => Arbitrary (SUL.Set a) where
  arbitrary = fmap E.fromList QC.arbitrary

instance (Arbitrary a, Ord a) => Arbitrary (SL.Set a) where
  arbitrary = fmap E.fromList QC.arbitrary

instance (Arbitrary k, Prim k, Ord k, Arbitrary v, Prim v) => Arbitrary (MUU.Map k v) where
  arbitrary = fmap E.fromList QC.arbitrary

instance (Arbitrary k, Prim k, Ord k, Arbitrary v) => Arbitrary (MUL.Map k v) where
  arbitrary = fmap E.fromList QC.arbitrary

instance (Arbitrary k, Ord k, Arbitrary v) => Arbitrary (MLL.Map k v) where
  arbitrary = fmap E.fromList QC.arbitrary

instance (Arbitrary k, Ord k, Enum k, Bounded k, Arbitrary v, Semigroup v, Eq v) => Arbitrary (DMLL.Map k v) where
  arbitrary = DMLL.fromListAppend <$> QC.vectorOf 10 arbitraryOrderedPairValue
  shrink x = map E.fromList (QC.shrink (E.toList x))

instance (Ord k, Enum k, Eq v, Bounded k, Arbitrary k, Arbitrary v) => Arbitrary (MIDBTS.Map k v) where
  arbitrary = liftA2 MIDBTS.fromList QC.arbitrary (QC.vectorOf 10 arbitraryOrderedPairValue)
    
instance (Arbitrary k, Ord k, Arbitrary v, Eq v, Semigroup v) => Arbitrary (MSL.Map k v) where
  arbitrary = do
    len <- QC.choose (0,4)
    xs <- QC.vectorOf len $ do
      n <- QC.choose (0,3)
      ys <- QC.vector n
      v <- QC.arbitrary
      return (SL.fromList ys, v)
    return (MSL.fromList xs)
  shrink x =
    [ MSL.fromList (drop 1 y)
    ]
    where y = MSL.toList x

instance (Arbitrary k, Prim k, Ord k, Enum k, Bounded k, Arbitrary v, Semigroup v, Eq v) => Arbitrary (DMUL.Map k v) where
  arbitrary = do
    sz <- QC.choose (0,10)
    k <- QC.arbitrary
    xs <- increasingOrderedPairsHelper sz k
    ys <- forM xs $ \(lo,hi) -> do
      v <- QC.arbitrary
      return (lo,hi,v)
    return (DMUL.fromListAppend ys)
  shrink x = map E.fromList (QC.shrink (E.toList x))
    
instance (Arbitrary a, Ord a, Enum a, Bounded a) => Arbitrary (DSL.Set a) where
  arbitrary = DSL.fromList <$> QC.vectorOf 7 arbitraryOrderedPair
  shrink x = map E.fromList (QC.shrink (E.toList x))

instance (Arbitrary a, Ord a, Enum a, Bounded a) => Arbitrary (DUSL.Set a) where
  arbitrary = do
    sz <- QC.choose (0,7)
    k <- QC.arbitrary
    foldMap (\(lo,hi) -> DUSL.singleton (Just lo) (Just hi)) <$> increasingOrderedPairsHelper sz k
    
increasingOrderedPairsHelper :: (Ord k, Enum k, Bounded k) => Int -> k -> Gen [(k,k)]
increasingOrderedPairsHelper n k = if n > 0
  then case atLeastTwoGreaterThan k of
    Nothing -> return []
    Just vals -> do
      lo <- QC.elements vals
      hi <- QC.elements (equalToOrGreaterThan lo)
      xs <- increasingOrderedPairsHelper (n - 1) hi
      return ((lo,hi) : xs)
  else return []

equalToOrGreaterThan :: (Ord a, Bounded a, Enum a) => a -> [a]
equalToOrGreaterThan a0 =
  let a1 = if a0 < maxBound then succ a0 else a0
      a2 = if a1 < maxBound then succ a1 else a1
      a3 = if a2 < maxBound then succ a2 else a2
   in [a0,a1,a2,a3]

atLeastTwoGreaterThan :: (Enum a, Bounded a, Ord a) => a -> Maybe [a]
atLeastTwoGreaterThan a0 = do
  if a0 < maxBound
    then
      let a1 = succ a0
       in if a1 < maxBound
            then
              let a2 = succ a1
                  a3 = if a2 < maxBound then succ a2 else a2
                  a4 = if a3 < maxBound then succ a3 else a3
               in Just [a2,a3,a4]
            else Nothing
    else Nothing

arbitraryOrderedPair :: (Ord k, Enum k, Bounded k, Arbitrary k) => Gen (k,k)
arbitraryOrderedPair = do
  a0 <- QC.arbitrary
  let a1 = if a0 < maxBound then succ a0 else a0
      a2 = if a1 < maxBound then succ a1 else a1
      a3 = if a2 < maxBound then succ a2 else a2
  a' <- QC.elements [a0,a1,a2,a3]
  return (a0,a')

arbitraryOrderedPairValue :: (Ord k, Enum k, Bounded k, Arbitrary k, Arbitrary v) => Gen (k,k,v)
arbitraryOrderedPairValue = do
  (lo,hi) <- arbitraryOrderedPair
  v <- QC.arbitrary
  return (lo,hi,v)

instance SG.Semigroup Word where
  w <> _ = w

instance SG.Semigroup Int where
  (<>) = (+)

instance Monoid Int where
  mempty = 0
  mappend = (SG.<>)
  
instance SG.Semigroup Integer where
  (<>) = (+)

instance Monoid Integer where
  mempty = 0
  mappend = (SG.<>)

deriving instance Arbitrary a => Arbitrary (SG.First a)

data Universe
  = UniverseInt
  | UniverseOrdering
  | UniverseBool
  | UniverseChar

data SingUniverse :: Universe -> Type where
  SingUniverseInt :: SingUniverse 'UniverseInt
  SingUniverseOrdering :: SingUniverse 'UniverseOrdering
  SingUniverseBool :: SingUniverse 'UniverseBool
  SingUniverseChar :: SingUniverse 'UniverseChar

deriving instance Show (SingUniverse u)

type instance Sing = SingUniverse

type family Interpret (u :: Universe) :: Type where
  Interpret 'UniverseInt = Integer
  Interpret 'UniverseOrdering = Ordering
  Interpret 'UniverseBool = Bool
  Interpret 'UniverseChar = Char

newtype Value :: Universe -> Type where
  Value :: Interpret u -> Value u

instance EqForeach Value where
  eqForeach SingUniverseInt (Value x) (Value y) = x == y
  eqForeach SingUniverseOrdering (Value x) (Value y) = x == y
  eqForeach SingUniverseBool (Value x) (Value y) = x == y
  eqForeach SingUniverseChar (Value x) (Value y) = x == y

instance OrdForeach Value where
  compareForeach SingUniverseInt (Value x) (Value y) = compare x y
  compareForeach SingUniverseOrdering (Value x) (Value y) = compare x y
  compareForeach SingUniverseBool (Value x) (Value y) = compare x y
  compareForeach SingUniverseChar (Value x) (Value y) = compare x y

instance ShowForeach Value where
  showsPrecForeach SingUniverseInt p (Value x) = showsPrec p x
  showsPrecForeach SingUniverseBool p (Value x) = showsPrec p x
  showsPrecForeach SingUniverseOrdering p (Value x) = showsPrec p x
  showsPrecForeach SingUniverseChar p (Value x) = showsPrec p x

instance SemigroupForeach Value where
  appendForeach SingUniverseInt (Value x) (Value y) = Value (x + y)
  appendForeach SingUniverseBool (Value x) (Value y) = Value (x && y)
  appendForeach SingUniverseOrdering (Value x) (Value y) = Value (x <> y)
  appendForeach SingUniverseChar (Value x) (Value _) = Value x

-- This type interpret the lowest two bits of the Word8
-- as the Universe value. Doing this is unsafe, but if the
-- data constructor of a type like this is not exported, it
-- is possible to build safe interfaces on top of this.
newtype UnboxedKey u = UnboxedKey Word8
  deriving (Show,Prim,Eq,Ord)

unboxedIntKey :: Word8 -> UnboxedKey 'UniverseInt
unboxedIntKey w = UnboxedKey (w * 4 + 0)

unboxedBoolKey :: Word8 -> UnboxedKey 'UniverseBool
unboxedBoolKey w = UnboxedKey (w * 4 + 1)

unboxedOrderingKey :: Word8 -> UnboxedKey 'UniverseOrdering
unboxedOrderingKey w = UnboxedKey (w * 4 + 2)

unboxedCharKey :: Word8 -> UnboxedKey 'UniverseChar
unboxedCharKey w = UnboxedKey (w * 4 + 3)

instance ToJSONKeyForall UnboxedKey where
  toJSONKeyForall = ToJSONKeyTextForall
    (\(UnboxedKey n) -> T.pack (show n))
    (\(UnboxedKey n) -> AEE.text (T.pack (show n)))

instance FromJSONKeyExists UnboxedKey where
  fromJSONKeyExists = AE.FromJSONKeyTextParser
    (\t -> case readMaybe (T.unpack t) of
      Nothing -> fail "Value, FromJSONKeyExists: bad value"
      Just w -> return (Exists (UnboxedKey w))
    )

instance FromJSONForeach Value where
  parseJSONForeach SingUniverseInt = fmap Value . AE.parseJSON 
  parseJSONForeach SingUniverseBool = fmap Value . AE.parseJSON
  parseJSONForeach SingUniverseOrdering = fmap Value . AE.parseJSON
  parseJSONForeach SingUniverseChar = fmap Value . AE.parseJSON

instance ToJSONForeach Value where
  toJSONForeach SingUniverseInt (Value a) = AE.toJSON a
  toJSONForeach SingUniverseBool (Value a) = AE.toJSON a
  toJSONForeach SingUniverseOrdering (Value a) = AE.toJSON a
  toJSONForeach SingUniverseChar (Value a) = AE.toJSON a

instance ToSing UnboxedKey where
  toSing (UnboxedKey w) = case mod w 4 of
    0 -> unsafeCoerce SingUniverseInt
    1 -> unsafeCoerce SingUniverseBool
    2 -> unsafeCoerce SingUniverseOrdering
    _ -> unsafeCoerce SingUniverseChar

instance ShowForall UnboxedKey where
  showsPrecForall = showsPrec

instance EqForall UnboxedKey where
  eqForall = (==)

instance EqForallPoly UnboxedKey where
  eqForallPoly (UnboxedKey a) (UnboxedKey b) = if a == b
    then unsafeCoerce WitnessedEqualityEqual
    else WitnessedEqualityUnequal

instance OrdForall UnboxedKey where
  compareForall = compare

instance OrdForallPoly UnboxedKey where
  compareForallPoly (UnboxedKey a) (UnboxedKey b) = case compare a b of
    LT -> WitnessedOrderingLT
    GT -> WitnessedOrderingGT
    EQ -> unsafeCoerce WitnessedOrderingEQ

data Key u = Key !Int !(SingUniverse u)
  deriving (Show)

instance ShowForall Key where
  showsPrecForall = showsPrec

instance ToSing Key where
  toSing (Key _ s) = s

instance EqForall Key where
  eqForall (Key i1 _) (Key i2 _) = i1 == i2

instance OrdForall Key where
  compareForall (Key i1 _) (Key i2 _) = compare i1 i2

instance EqForallPoly Key where
  eqForallPoly (Key i1 s1) (Key i2 s2) = if i1 == i2
    then case s1 of
      SingUniverseInt -> case s2 of
        SingUniverseInt -> WitnessedEqualityEqual
        _ -> WitnessedEqualityUnequal
      SingUniverseOrdering -> case s2 of
        SingUniverseOrdering -> WitnessedEqualityEqual
        _ -> WitnessedEqualityUnequal
      SingUniverseBool -> case s2 of
        SingUniverseBool -> WitnessedEqualityEqual
        _ -> WitnessedEqualityUnequal
      SingUniverseChar -> case s2 of
        SingUniverseChar -> WitnessedEqualityEqual
        _ -> WitnessedEqualityUnequal
    else WitnessedEqualityUnequal

instance EqForall SingUniverse where
  eqForall _ _ = True

instance OrdForall SingUniverse where
  compareForall _ _ = EQ

instance EqForallPoly SingUniverse where
  eqForallPoly SingUniverseInt SingUniverseInt = WitnessedEqualityEqual
  eqForallPoly SingUniverseInt _ = WitnessedEqualityUnequal
  eqForallPoly SingUniverseBool SingUniverseBool = WitnessedEqualityEqual
  eqForallPoly SingUniverseBool _ = WitnessedEqualityUnequal
  eqForallPoly SingUniverseOrdering SingUniverseOrdering = WitnessedEqualityEqual
  eqForallPoly SingUniverseOrdering _ = WitnessedEqualityUnequal
  eqForallPoly SingUniverseChar SingUniverseChar = WitnessedEqualityEqual
  eqForallPoly SingUniverseChar _ = WitnessedEqualityUnequal

instance OrdForallPoly SingUniverse where
  compareForallPoly SingUniverseInt SingUniverseInt      = WitnessedOrderingEQ
  compareForallPoly SingUniverseInt SingUniverseOrdering = WitnessedOrderingLT
  compareForallPoly SingUniverseInt SingUniverseBool     = WitnessedOrderingLT
  compareForallPoly SingUniverseInt SingUniverseChar     = WitnessedOrderingLT
  compareForallPoly SingUniverseOrdering SingUniverseInt      = WitnessedOrderingGT
  compareForallPoly SingUniverseOrdering SingUniverseOrdering = WitnessedOrderingEQ
  compareForallPoly SingUniverseOrdering SingUniverseBool     = WitnessedOrderingLT
  compareForallPoly SingUniverseOrdering SingUniverseChar     = WitnessedOrderingLT
  compareForallPoly SingUniverseBool SingUniverseInt      = WitnessedOrderingGT
  compareForallPoly SingUniverseBool SingUniverseOrdering = WitnessedOrderingGT
  compareForallPoly SingUniverseBool SingUniverseBool     = WitnessedOrderingEQ
  compareForallPoly SingUniverseBool SingUniverseChar     = WitnessedOrderingLT
  compareForallPoly SingUniverseChar SingUniverseInt      = WitnessedOrderingGT
  compareForallPoly SingUniverseChar SingUniverseOrdering = WitnessedOrderingGT
  compareForallPoly SingUniverseChar SingUniverseBool     = WitnessedOrderingGT
  compareForallPoly SingUniverseChar SingUniverseChar     = WitnessedOrderingEQ

instance OrdForallPoly Key where
  compareForallPoly (Key i1 s1) (Key i2 s2) = case compare i1 i2 of
    LT -> WitnessedOrderingLT
    GT -> WitnessedOrderingGT
    EQ -> compareForallPoly s1 s2

class ArbitraryDependentPair k v where
  arbitraryDependentPair :: Gen (DependentPair k v)

instance ArbitraryDependentPair k v => Arbitrary (DependentPair k v) where
  arbitrary = arbitraryDependentPair

instance ArbitraryDependentPair Key Value where
  arbitraryDependentPair = do
    (i :: Int) <- QC.choose (0, 10)
    QC.oneof
      [ DependentPair (Key i SingUniverseInt) . Value <$> QC.arbitrary
      , DependentPair (Key i SingUniverseBool) . Value <$> QC.arbitrary
      , DependentPair (Key i SingUniverseChar) . Value <$> QC.arbitrary
      , DependentPair (Key i SingUniverseOrdering) . Value <$> QC.arbitrary
      ]

instance ArbitraryDependentPair UnboxedKey Value where
  arbitraryDependentPair = do
    (i :: Word8) <- QC.choose (0, 10)
    QC.oneof
      [ DependentPair (unboxedIntKey i) . Value <$> QC.arbitrary
      , DependentPair (unboxedBoolKey i) . Value <$> QC.arbitrary
      , DependentPair (unboxedCharKey i) . Value <$> QC.arbitrary
      , DependentPair (unboxedOrderingKey i) . Value <$> QC.arbitrary
      ]
    
instance (ArbitraryDependentPair k v, OrdForallPoly k) => Arbitrary (DPMLL.Map k v) where
  arbitrary = do
    len <- QC.choose (0,35)
    DPMLL.fromList <$> QC.vectorOf len arbitraryDependentPair

instance (ArbitraryDependentPair k v, OrdForallPoly k, Universally k Prim, ApplyUniversally k Prim) => Arbitrary (DPMUL.Map k v) where
  arbitrary = do
    len <- QC.choose (0,35)
    DPMUL.fromList <$> QC.vectorOf len arbitraryDependentPair

instance Universally UnboxedKey Prim where
  universally _ _ _ x = x

instance ApplyUniversally UnboxedKey Prim where
  applyUniversallyLifted _ _ _ x = x
  applyUniversallyUnlifted _ _ _ x = x

-- very unsafe instance
instance PrimForall UnboxedKey where
  sizeOfForall# _ = sizeOf# (undefined :: UnboxedKey a)
  alignmentForall# _ = alignment# (undefined :: UnboxedKey a)
  indexByteArrayForall# = indexByteArray#
  readByteArrayForall# = readByteArray#
  writeByteArrayForall# = writeByteArray#
  setByteArrayForall# = setByteArray#
  readOffAddrForall# = readOffAddr#
  writeOffAddrForall# = writeOffAddr#
  indexOffAddrForall# = indexOffAddr#
  setOffAddrForall# = setOffAddr#