finitary-derive-2.0.0.0: src/Data/Finitary/Finiteness.hs
{-
- Copyright (C) 2019 Koz Ross <koz.ross@retro-freedom.nz>
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeApplications #-}
-- |
-- Module: Data.Finitary.Finiteness
-- Description: Newtype wrapper for deriving various typeclasses for
-- @Finitary@ types.
-- Copyright: (C) Koz Ross 2019
-- License: GPL version 3.0 or later
-- Maintainer: koz.ross@retro-freedom.nz
-- Stability: Experimental
-- Portability: GHC only
--
-- Knowing that a type @a@ is an instance of @Finitary@ gives us the knowledge
-- that there is an isomorphism between @a@ and @Finite n@ for some @KnownNat
-- n@. This gives us a lot of information, which we can exploit to automagically
-- derive a range of type class instances.
--
-- 'Finiteness' is a @newtype@ wrapper providing this functionality, while
-- 're-exporting' as many type class instances of the underlying type as
-- possible. It is designed for use with @DerivingVia@ - an example of use:
--
-- > {-# LANGUAGE DerivingVia #-}
-- > {-# LANGUAGE DeriveAnyClass #-}
-- > {-# LANGUAGE DeriveGeneric #-}
-- >
-- > import GHC.Generics
-- > import Data.Finitary
-- > import Data.Finitary.Finiteness
-- > import Data.Word
-- > import Control.DeepSeq
-- > import Data.Hashable
-- > import Data.Binary
-- >
-- > data Foo = Bar | Baz (Word8, Word8) | Quux Word16
-- > deriving (Eq, Generic, Finitary)
-- > deriving (Ord, Bounded, NFData, Hashable, Binary) via (Finiteness Foo)
--
-- Currently, the following type class instances can be derived in this manner:
--
-- * 'Ord'
-- * 'Bounded'
-- * 'NFData'
-- * 'Hashable'
-- * 'Binary'
--
-- Additionally, 'Finiteness' \'forwards\' definitions of the following type
-- classes:
--
-- * 'Eq'
-- * 'Show'
-- * 'Read'
-- * 'Typeable'
-- * 'Data'
-- * 'Semigroup'
-- * 'Monoid'
module Data.Finitary.Finiteness
(
Finiteness(..)
) where
import GHC.TypeNats
import Data.Typeable (Typeable)
import Data.Data (Data)
import Data.Finitary (Finitary(..))
import Data.Ord (comparing)
import Control.DeepSeq (NFData(..))
import Data.Hashable (Hashable(..))
import Data.Binary (Binary(..))
-- | Essentially 'Data.Functor.Identity' with a different name. Named this way due to the
-- wordplay you get from use with @DerivingVia@.
newtype Finiteness a = Finiteness { unFiniteness :: a }
deriving (Eq, Show, Read, Typeable, Data, Functor, Semigroup, Monoid)
-- | 'Finiteness' merely replicates the @Finitary@ behaviour of the underlying
-- type.
instance (Finitary a) => Finitary (Finiteness a) where
type Cardinality (Finiteness a) = Cardinality a
{-# INLINE fromFinite #-}
fromFinite = Finiteness . fromFinite
{-# INLINE toFinite #-}
toFinite = toFinite . unFiniteness
{-# INLINE start #-}
start = Finiteness start
{-# INLINE end #-}
end = Finiteness end
{-# INLINE previous #-}
previous = fmap Finiteness . previous . unFiniteness
{-# INLINE next #-}
next = fmap Finiteness . next . unFiniteness
-- | 'Ord' can be derived by deferring to the order on @Finite (Cardinality a)@.
instance (Finitary a) => Ord (Finiteness a) where
{-# INLINE compare #-}
compare (Finiteness x) (Finiteness y) = comparing toFinite x y
-- | Since any inhabited 'Finitary' type is also 'Bounded', we can forward this
-- definition also.
instance (Finitary a, 1 <= Cardinality a) => Bounded (Finiteness a) where
{-# INLINE minBound #-}
minBound = Finiteness start
{-# INLINE maxBound #-}
maxBound = Finiteness end
-- | We can force evaluation of a 'Finitary' type by converting it to its index.
instance (Finitary a) => NFData (Finiteness a) where
{-# INLINE rnf #-}
rnf = rnf . toFinite . unFiniteness
-- | Any 'Finitary' type can be hashed by hashing its index.
instance (Finitary a) => Hashable (Finiteness a) where
{-# INLINE hashWithSalt #-}
hashWithSalt salt = hashWithSalt salt . fromIntegral @_ @Integer . toFinite . unFiniteness
-- | Any 'Finitary' type can be converted to a binary representation by
-- converting its index.
instance (Finitary a) => Binary (Finiteness a) where
{-# INLINE put #-}
put = put . fromIntegral @_ @Integer . toFinite . unFiniteness
{-# INLINE get #-}
get = Finiteness . fromFinite . fromIntegral @Integer <$> get