packages feed

falsify-0.4.0: src/Test/Falsify/Marked.hs

-- | Marked elements
--
-- Intended for qualified import.
--
-- > import Test.Falsify
-- > import qualified Test.Falsify.Marked as Marked
module Test.Falsify.Marked (
    Mark(..)
  , Marked(..)
    -- * Generation
  , selectAllKept
    -- * Queries
  , countKept
  , shouldKeep
  ) where

import Control.Selective
import Data.Foldable (toList)
import Data.Maybe (mapMaybe)

{-------------------------------------------------------------------------------
  Definition
-------------------------------------------------------------------------------}

-- | Should an element in a container be kept or dropped?
--
-- See also t'Marked'.
data Mark = Keep | Drop
  deriving stock (Show, Eq, Ord)

-- | Marked element in a container
--
-- Marking elements can be a useful technique for dropping elements from a
-- container.
--
-- * Locally marking elements to 'Drop' makes it possible to enforce some global
--   constraints about minimum number of required elements before /actually/
--   dropping them (see 'selectAllKept').
--   Example: 'Test.Falsify.Generator.list'.
--
-- * For containers where we cannot remove random elements, the marks can be
--   used for \"outwards propagation\": if /this/ element is dropped, then
--   /those/ elements must also be dropped.
--   Example: 'Test.Falsify.Generator.tree'.
data Marked f a = Marked {
      getMark :: Mark
    , unmark  :: f a
    }
  deriving stock (Show, Eq, Ord)

{-------------------------------------------------------------------------------
  Generation
-------------------------------------------------------------------------------}

selectKept :: Selective f => Marked f a -> f (Maybe a)
selectKept (Marked mark gen) =
    ifS (pure $ mark == Keep)
        (Just <$> gen)
        (pure Nothing)

-- | Traverse the argument, generating all values marked 'Keep', and replacing
-- all values marked 'Drop' by 'Nothing'
selectAllKept ::
     (Traversable t, Selective f)
  => t (Marked f a) -> f (t (Maybe a))
selectAllKept = traverse selectKept

{-------------------------------------------------------------------------------
  Queries
-------------------------------------------------------------------------------}

-- | Count how many elements will be kept
countKept :: Foldable t => t (Marked f a) -> Word
countKept = fromIntegral . length . mapMaybe shouldKeep . toList

-- | The element /if/ it should be kept
shouldKeep :: Marked f a -> Maybe (f a)
shouldKeep (Marked Keep x) = Just x
shouldKeep (Marked Drop _) = Nothing