packages feed

pure-borrow-0.1.0.0: test/Data/HashMap/RobinHood/Mutable/LinearSpec/Cases.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE QualifiedDo #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}

-- | Scripted mutation sequences shared by the Robin Hood hash table specs.
module Data.HashMap.RobinHood.Mutable.LinearSpec.Cases (
  module Data.HashMap.RobinHood.Mutable.LinearSpec.Cases,
) where

import Control.Monad.Borrow.Pure (linearly)
import Control.Syntax.DataFlow qualified as DataFlow
import Data.HashMap.RobinHood.Mutable.Linear as HM
import Data.Map.Strict qualified as Map
import Data.Unrestricted.Linear qualified as Ur
import Prelude.Linear hiding (lookup)
import Prelude qualified as NonLinear

withNewEmptyHashMap :: (HashMap k v %1 -> Ur r) %1 -> Ur r
withNewEmptyHashMap f = linearly $ f . new 16

data Case1Result = Case1Result
  { initOneResident :: Maybe Int
  , newOneResident :: Maybe Int
  , initTwoResident :: Maybe Int
  , deletedOneResident :: Maybe Int
  , finalResult :: [(String, Int)]
  }
  deriving (Show, NonLinear.Eq, NonLinear.Ord)

-- | Insert, look up, insert again and delete, then materialize.
case1 :: HashMap String Int %1 -> Ur Case1Result
case1 hm =
  HM.insert "One" 1 hm & \(Ur initOneResident, hm) ->
    HM.lookup "One" hm & \(Ur newOneResident, hm) ->
      HM.insert "Two" 2 hm & \(Ur initTwoResident, hm) ->
        HM.delete "One" hm & \(Ur deletedOneResident, hm) ->
          HM.toList hm & \(Ur finalResult) ->
            Ur Case1Result {..}

-- | Bulk insert, bulk delete across a growth, then bulk insert again.
case2 :: HashMap String Int %1 -> Ur [(String, Int)]
case2 hm = DataFlow.do
  hm <- HM.insertMany [(show i, i) | i <- [1 .. 128]] hm
  hm <-
    foldl'
      (\hm i -> move i & \(Ur i) -> uncurry lseq (HM.delete (show i) hm))
      hm
      [16 .. 256 :: Int]
  hm <- HM.insertMany [(show i, i) | i <- [129 .. 256]] hm
  HM.toList hm

data Case3Result = Case3Result
  { iniOneReside :: Maybe Int
  , iniOneResideExpected :: Maybe Int
  , oneBeforeBulkInsert :: Maybe Int
  , oneBeforeBulkInsertExpected :: Maybe Int
  , oneAfterBulkInsert :: Maybe Int
  , oneAfterBulkInsertExpected :: Maybe Int
  , sixteenAfterBulkInsert :: Maybe Int
  , sixteenAfterBulkInsertExpected :: Maybe Int
  , sixteenAfterBulkDelete :: Maybe Int
  , sixteenAfterBulkDeleteExpected :: Maybe Int
  , poppedSixteen :: Maybe Int
  , poppedSixteenExpected :: Maybe Int
  , finalSixteen :: Maybe Int
  , finalSixteenExpected :: Maybe Int
  , finalResult :: Map.Map String Int
  , expectedResult :: Map.Map String Int
  }
  deriving (Show, NonLinear.Eq, NonLinear.Ord)

-- | Interleave lookups with the bulk mutations of 'case2'.

{- NOTE: written with @case@ rather than as @DataFlow.do@, unlike 'case2'.

Every step here binds through a pattern that mentions 'Ur', and 'Ur' is
declared in GADT syntax, so GHC 9.10 rates such a pattern refutable in a @do@
statement and demands a `fail` that "Control.Syntax.DataFlow" does not
provide. GHC 9.12 accepts the same code. A @case@ alternative carries no such
rule, so this compiles on every supported compiler; 'case2' can keep its @do@
because it only ever binds plain variables.
-}
case3 :: HashMap String Int %1 -> Ur Case3Result
case3 hm = case HM.insert "1" 919 hm of
  (Ur iniOneReside, hm) -> case HM.lookup "1" hm of
    (Ur oneBeforeBulkInsert, hm) -> case HM.insertMany [(show i, i) | i <- [1 .. 128]] hm of
      hm -> case HM.lookup "1" hm of
        (Ur oneAfterBulkInsert, hm) -> case HM.lookup "16" hm of
          (Ur sixteenAfterBulkInsert, hm) ->
            case foldl'
              (\hm i -> move i & \(Ur i) -> uncurry lseq (HM.delete (show i) hm))
              hm
              [16 .. 256 :: Int] of
              hm -> case HM.lookup "16" hm of
                (Ur sixteenAfterBulkDelete, hm) -> case HM.insertMany [(show i, i) | i <- [129 .. 256]] hm of
                  hm -> case HM.insert "16" 9181 hm of
                    (Ur poppedSixteen, hm) -> case HM.lookup "16" hm of
                      (Ur finalSixteen, hm) -> case Map.fromList `Ur.lift` HM.toList hm of
                        Ur finalResult ->
                          let iniOneResideExpected = Nothing
                              oneBeforeBulkInsertExpected = Just 919
                              oneAfterBulkInsertExpected = Just 1
                              sixteenAfterBulkInsertExpected = Just 16
                              sixteenAfterBulkDeleteExpected = Nothing
                              poppedSixteenExpected = Nothing
                              finalSixteenExpected = Just 9181
                              expectedResult = Map.fromList $ [(show i, i) | i <- [2 .. 15] <> [129 .. 256]] <> [("1", 1), ("16", 9181)]
                           in Ur Case3Result {..}