packages feed

hedgehog-lens-0.1.0.0: src/Hedgehog/Lens.hs

{-# LANGUAGE RankNTypes #-}

module Hedgehog.Lens
  ( wellFormedPrism,
    wellFormedLens,
    wellFormedIso,
    prismExample,
  )
where

import Control.Lens
  ( Iso',
    Lens',
    Prism',
    matching,
    preview,
    review,
    set,
    view,
  )
import Control.Monad (Monad (return))
import Data.Either (Either (Left, Right))
import Data.Eq (Eq)
import Data.Function ((.))
import Data.Maybe (Maybe (Just))
import Hedgehog (Gen, PropertyT, annotate, forAll, (===))
import Text.Show (Show)

-- | Checks whether a prism respects the well-formedness laws:
--
-- 1) Get-Set Prism Law
-- 2) Set-Get Prism Law
wellFormedPrism ::
  Monad m =>
  (Show large, Eq large) =>
  (Show small, Eq small) =>
  Gen large ->
  Gen small ->
  -- | Prism signifying that the
  --          @small@ type is a subset of the @large@ type
  Prism' large small ->
  PropertyT m ()
wellFormedPrism genLarge genSmall o =
  do
    getSetPrismLaw genLarge o
    setGetPrismLaw genSmall o

-- | Get-Set Prism Law
--
-- @
-- 'matching' o s ≡ 'Right' a => 'review' o a ≡ s
-- @
getSetPrismLaw ::
  Monad m =>
  (Show large, Eq large) =>
  (Show small, Eq small) =>
  Gen large ->
  Prism' large small ->
  PropertyT m ()
getSetPrismLaw genLarge o =
  do
    large <- forAll genLarge
    case matching o large of
      Right small ->
        do
          annotate "The get-set law must hold for a Prism"
          review o small === large
      Left _ -> return ()

-- | Set-Get Prism Law
--
-- @
-- 'matching' o ('review' o b) ≡ 'Right' b
-- @
setGetPrismLaw ::
  Monad m =>
  (Show large, Eq large) =>
  (Show small, Eq small) =>
  Gen small ->
  Prism' large small ->
  PropertyT m ()
setGetPrismLaw genSmall o =
  do
    small <- forAll genSmall
    annotate "The set-get law must hold for a Prism"
    matching o (review o small) === Right small

-- | Checks whether a lens respects the well-formedness laws:
--
-- 1) Get-Put Lens Law
-- 2) Put-Get Lens Law
-- 3) Put-Put Lens Law
wellFormedLens ::
  Monad m =>
  (Show large, Eq large) =>
  (Show small, Eq small) =>
  Gen large ->
  Gen small ->
  -- | Lens signifying that the @small@
  --          type is a constituent part of the @large@ type
  Lens' large small ->
  PropertyT m ()
wellFormedLens genLarge genSmall o =
  do
    getPutLensLaw genLarge genSmall o
    putGetLensLaw genLarge o
    putPutLensLaw genLarge genSmall o

-- | Get-Put Lens Law
-- You get back what you put in:
--
-- @
-- 'view' l ('set' l v s) ≡ v
-- @
getPutLensLaw ::
  Monad m =>
  (Show large, Eq large) =>
  (Show small, Eq small) =>
  Gen large ->
  Gen small ->
  Lens' large small ->
  PropertyT m ()
getPutLensLaw genLarge genSmall o =
  do
    large <- forAll genLarge
    small <- forAll genSmall
    annotate "The set-get law must hold for a Lens"
    view o (set o small large) === small

-- | Put-Get Lens Law
-- Putting back what you got doesn't change anything:
--
-- @
-- 'set' l ('view' l s) s ≡ s
-- @
putGetLensLaw ::
  Monad m =>
  (Show large, Eq large) =>
  (Show small, Eq small) =>
  Gen large ->
  Lens' large small ->
  PropertyT m ()
putGetLensLaw genLarge o =
  do
    large <- forAll genLarge
    annotate "The get-set law must hold for a Lens"
    set o (view o large) large === large

-- | Put-Put Lens Law
-- Setting twice is the same as setting once:
--
-- @
-- 'set' l v' ('set' l v s) ≡ 'set' l v' s
-- @
putPutLensLaw ::
  Monad m =>
  (Show large, Eq large) =>
  (Show small, Eq small) =>
  Gen large ->
  Gen small ->
  Lens' large small ->
  PropertyT m ()
putPutLensLaw genLarge genSmall o =
  do
    large <- forAll genLarge
    small1 <- forAll genSmall
    small2 <- forAll genSmall
    annotate "The set-set law must hold for a Lens"
    set o small2 (set o small1 large) === set o small2 large

-- | Checks whether an isomorphism respects the well-formedness laws:
--
-- 1) Set-Get Iso Law
-- 2) Get-Set Iso Law
wellFormedIso ::
  Monad m =>
  (Show a, Eq a) =>
  (Show b, Eq b) =>
  Gen a ->
  Gen b ->
  -- | Isomorphism signifying that types
  --          @a@ and @b@ are basically the same thing
  Iso' a b ->
  PropertyT m ()
wellFormedIso genA genB o =
  do
    setGetIsoLaw genB o
    getSetIsoLaw genA o

-- | Set-Get Iso Law
-- You get back what you put in:
--
-- @
-- 'view' i . 'review' i ≡ 'id'
-- @
setGetIsoLaw ::
  Monad m =>
  (Show a, Eq a) =>
  (Show b, Eq b) =>
  Gen b ->
  Iso' a b ->
  PropertyT m ()
setGetIsoLaw genB o =
  do
    b <- forAll genB
    annotate "The set-get law must hold for an Iso"
    (view o . review o) b === b

-- | Get-Set Iso Law
-- You get back what you put in:
--
-- @
-- 'review' i . 'view' i ≡ 'id'
-- @
getSetIsoLaw ::
  Monad m =>
  (Show a, Eq a) =>
  (Show b, Eq b) =>
  Gen a ->
  Iso' a b ->
  PropertyT m ()
getSetIsoLaw genA o =
  do
    a <- forAll genA
    annotate "The get-set law must hold for an Iso"
    (review o . view o) a === a

-- | Assert that a prism matches for a particular set of values:
-- A 'review' of the @small@ value should produce the @large@ value, and
-- a 'preview' of the @large@ value should produce the @small@ value.
prismExample ::
  Monad m =>
  (Show large, Eq large) =>
  (Show small, Eq small) =>
  -- | Prism signifying that the
  --          @small@ type is a subset of the @large@ type
  Prism' large small ->
  large ->
  small ->
  PropertyT m ()
prismExample o large small =
  do
    review o small === large
    preview o large === Just small