packages feed

higgledy-0.3.0.0: test/Main.hs

{-# LANGUAGE AllowAmbiguousTypes       #-}
{-# LANGUAGE BlockArguments            #-}
{-# LANGUAGE ConstraintKinds           #-}
{-# LANGUAGE DataKinds                 #-}
{-# LANGUAGE DeriveGeneric             #-}
{-# LANGUAGE FlexibleContexts          #-}
{-# LANGUAGE MonoLocalBinds            #-}
{-# LANGUAGE RankNTypes                #-}
{-# LANGUAGE ScopedTypeVariables       #-}
{-# LANGUAGE TypeApplications          #-}
module Main where

import Control.Lens (Lens', (.~), (^.))
import Data.Barbie
import Data.Barbie.Constraints (Dict)
import Data.Function ((&), on)
import Data.Functor.Identity (Identity (..))
import Data.Functor.Product (Product (..))
import Data.Generic.HKD
import Data.Monoid (Last (..))
import GHC.Generics
import Test.DocTest
import Test.Hspec
import Test.QuickCheck

type Partial a = HKD a Last
type WTF     a = HKD a []

main :: IO ()
main = do
  doctest ["src", "test"]

  hspec do
    describe "Unnamed" do
      eq         @(Partial Triple)
      ord        @(Partial Triple)
      semigroup  @(Partial Triple)
      idempotent @(Partial Triple)
      monoid     @(Partial Triple)

      eq        @(WTF Triple)
      ord       @(WTF Triple)
      semigroup @(WTF Triple)
      monoid    @(WTF Triple)

      lens @(Partial Triple) (position @1)
      lens @(Partial Triple) (position @2)
      lens @(Partial Triple) (position @3)

      lens @(WTF Triple) (position @1)
      lens @(WTF Triple) (position @2)
      lens @(WTF Triple) (position @3)

    describe "Named" do
      eq         @(Partial Person)
      ord        @(Partial Person)
      semigroup  @(Partial Person)
      idempotent @(Partial Person)
      monoid     @(Partial Person)

      eq        @(WTF Person)
      ord       @(WTF Person)
      semigroup @(WTF Person)
      monoid    @(WTF Person)

      lens @(WTF Person) (position @1)
      lens @(WTF Person) (position @2)
      lens @(WTF Person) (position @3)

      lens @(Partial Person) (field @"name")
      lens @(Partial Person) (field @"age")
      lens @(Partial Person) (field @"likesDogs")

      lens @(WTF Person) (field @"name")
      lens @(WTF Person) (field @"age")
      lens @(WTF Person) (field @"likesDogs")

-------------------------------------------------------------------------------

data Person
  = Person
      { name      :: String
      , age       :: Int
      , likesDogs :: Bool
      }
  deriving (Eq, Generic, Ord, Show)

data Triple
  = Triple String Bool ()
  deriving (Eq, Generic, Ord, Show)

instance Arbitrary Person where
  arbitrary = Person <$> arbitrary <*> arbitrary <*> arbitrary

instance CoArbitrary Person
instance Function    Person

instance Arbitrary Triple where
  arbitrary = Triple <$> arbitrary <*> arbitrary <*> arbitrary

instance CoArbitrary Triple
instance Function    Triple

-------------------------------------------------------------------------------

eq
  :: forall a. (Arbitrary a, CoArbitrary a, Eq a, Function a, Show a)
  => SpecWith ()

eq = describe "Eq" do
  it "is reflexive" $ property \(x :: a) ->
    x == x

  it "is symmetric"  $ property \(x :: a) y ->
    (x == y) == (y == x)

  it "is transitive" $ property \(x :: a) y z ->
    not (x == y && y == z) || (x == z)

  it "substitutes" $ property \(x :: a) y (Fun _ f :: Fun a Int) ->
    not (x == y) || (f x == f y)

ord :: forall a. (Arbitrary a, Ord a, Show a) => SpecWith ()
ord = describe "Ord" do
  it "is transitive" $ property \(x :: a) y z ->
    not (x <= y && y <= z) || (x <= z)

  it "is reflexive" $ property \(x :: a) ->
    x <= x

  it "is antisymmetric" $ property \(x :: a) y ->
    not (x <= y && y <= x) || (x == y)

semigroup :: forall a. (Arbitrary a, Eq a, Semigroup a, Show a) => SpecWith ()
semigroup = describe "Semigroup" do
  it "is associative" $ property \(x :: a) y z ->
    x <> (y <> z) == (x <> y) <> z

idempotent :: forall a. (Arbitrary a, Eq a, Semigroup a, Show a) => SpecWith ()
idempotent = describe "Idempotence" do
  it "has right idempotence" $ property \(x :: a) y ->
    x <> y <> y == x <> y

monoid :: forall a. (Arbitrary a, Eq a, Monoid a, Show a) => SpecWith ()
monoid = describe "Monoid" do
  it "has left identity"  $ property \(x :: a) -> mempty <> x == x
  it "has right identity" $ property \(x :: a) -> x <> mempty == x

lens
  :: forall s a
   . ( Arbitrary s, Arbitrary a
     , Show      s, Show      a
     , Eq        a, Eq        s
     )
  => Lens' s a
  -> SpecWith ()

lens l = describe "Lens laws" do
  it "- get l . set l x == x" $ property \(s :: s) (a :: a) ->
    (s & l .~ a) ^. l == a

  it "- set l (get l s) == s" $ property \(s :: s) ->
    (s & l .~ (s ^. l)) == s

  it "- set l b . set l a == set l b" $ property \(s :: s) (a :: a) (b :: a) ->
    (s & l .~ a & l .~ b) == (s & l .~ b)

-------------------------------------------------------------------------------

partials
  :: forall a
   . ( Arbitrary a
     , Show      a
     , Ord       a
     , Generic   a
     , Construct Last a
     , Construct [] a
     , Ord       (Partial a)
     , Ord       (WTF     a)
     )
  => SpecWith ()

partials = describe "HKD" do
  describe "Eq" do
    it "is monotonic with respect to ordering (Partial)" $ property \(x :: a) y ->
      (x <= y) == ((<=) `on` deconstruct @Last) x y

    it "is monotonic with respect to ordering (WTF)" $ property \(x :: a) y ->
      (x <= y) == ((<=) `on` deconstruct @[]) x y

    it "round-trips" $ property \(x :: a) ->
      construct (deconstruct @Last x) == pure x

    it "round-trips" $ property \(x :: a) ->
      construct (deconstruct @[] x) == pure x

-- Just to test that `baddDicts` does what it's told.
data Y = Y { getY :: Int } deriving (Generic, Show)

test :: HKD Y (Product (Dict Num) Identity)
test = baddDicts test
  where
    test :: HKD Y Identity
    test = deconstruct @Identity (Y 10)