packages feed

proarrow-0.1.0.0: test/Props/Hask.hs

{-# LANGUAGE OverloadedLists #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module Props.Hask where

import Data.Falsify.ConcreteFun qualified as ConcreteFun
import Data.Kind (Type)
import Data.List (intercalate)
import Data.Void (Void)
import Proarrow.Category.Instance.Opposite (OPPOSITE)
import Proarrow.Category.Monoidal.Closed (ExpRep)
import Proarrow.Functor (Prelude (..))
import Proarrow.Profunctor.Instance.Costar (Costar)
import Proarrow.Profunctor.Instance.Star (Star)
import Proarrow.Profunctor.Representable (Rep)
import Test.Falsify.Generator (Function, choose, function, list)
import Test.Falsify.Range (inclusive)
import Test.Tasty (TestTree, testGroup)
import Type.Reflection (Typeable, typeRep)
import Prelude hiding (elem, (.))

import Control.Monad (unless)
import Proarrow.Core (Promonad (..), type (+->))
import Proarrow.Monoid qualified as Monoid
import Proarrow.Testing
  ( GenTotal (..)
  , TestOb'
  , Testable (..)
  , TestableProfunctor
  , TestableType (..)
  , TestingEqShow (..)
  , genSomeDef
  , invmap
  , oneElem
  , optGen
  , pattern GenNonEmpty
  )
import Proarrow.Testing.Laws
import Test.Falsify (testFailed)
import Test.Tasty.Falsify (testProperty)

test :: TestTree
test =
  testGroup
    "Hask"
    [ testCategory @Type
    , testTerminalObject @Type
    , testInitialObject @Type
    , testBinaryProducts @Type (\r -> r)
    , testCartesian @Type (\r -> r) (\r -> r)
    , testMonoidal @Type (\r -> r)
    , testSymMonoidal @Type (\r -> r)
    , testCopyDiscard @Type (\r -> r)
    , testBinaryCoproducts @Type (\r -> r)
    , testDistributive @Type (\r -> r) (\r -> r)
    , testClosed @Type (\r -> r) (\r -> r)
    , testFrobenius @() (\r -> r)
    , testProperty "list monoid is not Frobenius: copy-comonoid breaks speciality" $
        unless
          ((Monoid.mappend . Monoid.comult @[()]) [()] /= [()])
          (testFailed "speciality unexpectedly held for [()]")
    , testProfunctor @(Rep (ExpRep :: (OPPOSITE Type, Type) +-> Type))
    , testProfunctor @(Star (Prelude Maybe) :: Type +-> Type)
    , testPromonad @(Star (Prelude Maybe) :: Type +-> Type)
    , testRepresentable @(Star (Prelude Maybe) :: Type +-> Type) (\r -> r)
    , testMonoidalProfunctor @(Star (Prelude Maybe) :: Type +-> Type) (\r -> r) (\r -> r)
    , testCorepresentable @(Costar (Prelude Maybe) :: Type +-> Type) (\r -> r)
    ]

instance Testable Type where
  type TestOb a = (TestableType a, Typeable a, Function a)
  showOb @a = show (typeRep @a)
  genSome = genSomeDef @'[Bool, (Bool, Bool), Maybe Bool, Void]

instance TestableProfunctor (->)

instance TestableType Bool where
  gen = optGen [False, True]
instance TestableType () where
  gen = oneElem ()
instance TestableType Void where
  gen = GenEmpty \case {}
instance TestingEqShow Bool
instance TestingEqShow ()
instance TestingEqShow Void

instance (TestableType a, TestableType b) => TestableType (a, b) where
  gen = case (gen @a, gen @b) of
    (GenEmpty f, _) -> GenEmpty (f . fst)
    (_, GenEmpty g) -> GenEmpty (g . snd)
    (GenNonEmpty ga, GenNonEmpty gb) -> GenNonEmpty (liftA2 (,) ga gb)
instance (TestingEqShow a, TestingEqShow b) => TestingEqShow (a, b) where
  eqP (l1, l2) (r1, r2) = liftA2 (&&) (eqP l1 r1) (eqP l2 r2)
  showP (a, b) = "(" ++ showP a ++ ", " ++ showP b ++ ")"

instance (TestableType a, TestableType b) => TestableType (Either a b) where
  gen = case (gen @a, gen @b) of
    (GenEmpty f, GenEmpty g) -> GenEmpty (either f g)
    (GenNonEmpty ga, GenEmpty _) -> GenNonEmpty (Left <$> ga)
    (GenEmpty _, GenNonEmpty gb) -> GenNonEmpty (Right <$> gb)
    (GenNonEmpty ga, GenNonEmpty gb) -> GenNonEmpty (choose (Left <$> ga) (Right <$> gb))
instance (TestingEqShow a, TestingEqShow b) => TestingEqShow (Either a b) where
  eqP (Left l) (Left r) = eqP l r
  eqP (Right l) (Right r) = eqP l r
  eqP _ _ = pure False
  showP (Left a) = "Left " ++ showP a
  showP (Right b) = "Right " ++ showP b

instance (TestableType a) => TestableType (Maybe a) where
  gen = case gen @a of
    GenEmpty _ -> oneElem Nothing
    GenNonEmpty ga -> GenNonEmpty (choose (pure Nothing) (Just <$> ga))
instance (TestingEqShow a) => TestingEqShow (Maybe a) where
  eqP Nothing Nothing = pure True
  eqP (Just l) (Just r) = eqP l r
  eqP _ _ = pure False
  showP Nothing = "Nothing"
  showP (Just a) = "Just " ++ showP a

instance (TestingEqShow a) => TestingEqShow [a] where
  eqP l r = if length l /= length r then pure False else foldr (liftA2 (&&)) (pure True) (zipWith eqP l r)
  showP xs = "[" ++ intercalate ", " (map showP xs) ++ "]"
instance (TestableType a) => TestableType [a] where
  gen = case gen @a of
    GenEmpty _ -> GenNonEmpty (pure [])
    GenNonEmpty g -> GenNonEmpty (list (inclusive (0, 4)) g)

-- Hard to write and also unused instances.
instance Function (a -> b) where
  function = error "Should not be used"

instance TestableProfunctor (Rep (ExpRep :: (OPPOSITE Type, Type) +-> Type))

instance (TestableType (f a)) => TestableType (Prelude f a) where
  gen = invmap Prelude unPrelude gen
instance (TestingEqShow (f a)) => TestingEqShow (Prelude f a) where
  eqP (Prelude l) (Prelude r) = eqP l r
  showP (Prelude f) = showP f
instance (Function (f a)) => Function (Prelude f a) where
  function = fmap (ConcreteFun.map unPrelude Prelude) . function

instance (Functor f, Typeable f, forall b. (TestOb b) => TestOb' (f b)) => TestableProfunctor (Star (Prelude f))

instance (Functor f, Typeable f, forall b. (TestOb b) => TestOb' (f b)) => TestableProfunctor (Costar (Prelude f))