fastsum-0.1.0.0: src/Data/Sum.hs
{-# LANGUAGE AllowAmbiguousTypes, TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ConstraintKinds, DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_HADDOCK prune, ignore-exports #-}
{-|
Module : Data.Sum
Description : Open sums (type-indexed co-products) for extensible effects.
Copyright : Allele Dev 2015
License : BSD-3
Maintainer : allele.dev@gmail.com
Stability : experimental
Portability : POSIX
All operations are constant-time, and there is no Typeable constraint.
This is a variation of Kiselyov's OpenUnion5.hs, which relies on
overlapping instances instead of closed type families. Closed type
families have their problems: overlapping instances can resolve even
for unground types, but closed type families are subject to a strict
apartness condition.
-}
module Data.Sum
( -- * The fundamental sum-of-products type
Sum
-- * Creating and extracting sums from products
, inject
, project
-- * Operating on sums' effects lists
, decompose
, decomposeLast
, weaken
-- * Membership prodicates
, Element
, type(:<)
, Elements
, type(:<:)
, ElemIndex
-- * Typeclass application.
, Apply(..)
, apply'
, apply2
) where
import Data.Functor.Classes (Eq1(..), eq1, Ord1(..), compare1, Show1(..), showsPrec1)
import Data.Hashable (Hashable(..))
import Data.Hashable.Lifted (Hashable1(..), hashWithSalt1)
import Data.Maybe (fromMaybe)
import Data.Sum.Templates
import GHC.Exts (Constraint)
import GHC.Prim (Proxy#, proxy#)
import GHC.TypeLits
import Unsafe.Coerce(unsafeCoerce)
pure [mkElemIndexTypeFamily 150]
infixr 5 :<
-- | The fundamental sum type over a type-level list of products @r@
-- and an annotation type @v@. The constructor is not exported;
-- use 'inject' to create a 'Sum'.
data Sum (r :: [ * -> * ]) (v :: *) where
-- | Strong Sum (Existential with the evidence) is an open sum
-- t is can be a GADT and hence not necessarily a Functor.
-- Int is the index of t in the list r; that is, the index of t in the
-- universe r.
Sum :: {-# UNPACK #-} !Int -> t v -> Sum r v
unsafeInject :: Int -> t v -> Sum r v
unsafeInject = Sum
{-# INLINE unsafeInject #-}
unsafeProject :: Int -> Sum r v -> Maybe (t v)
unsafeProject n (Sum n' x) | n == n' = Just (unsafeCoerce x)
| otherwise = Nothing
{-# INLINE unsafeProject #-}
newtype P (t :: * -> *) (r :: [* -> *]) = P { unP :: Int }
infixr 5 :<:
-- | An @Elements ms r@ constraint proves that @r@ contains
-- all of the elements in @ms@.
type family Elements (ms :: [* -> *]) r :: Constraint where
Elements (t ': cs) r = (Element t r, Elements cs r)
Elements '[] r = ()
-- | An infix synonym for 'Elements'.
type (ts :<: r) = Elements ts r
-- | Inject a functor into a type-aligned sum.
inject :: forall e r v. (e :< r) => e v -> Sum r v
inject = unsafeInject (unP (elemNo :: P e r))
{-# INLINE inject #-}
-- | Maybe project a functor out of a type-aligned sum.
project :: forall e r v. (e :< r) => Sum r v -> Maybe (e v)
project = unsafeProject (unP (elemNo :: P e r))
{-# INLINE project #-}
-- | Attempts to extract the head type @e@ from a @Sum@. Returns
-- @Right@ on success, and a @Sum@ without @e@ otherwise. You can
-- repeatedly apply this and apply 'decomposeLast' when you have @Sum
-- '[e]@ to get typesafe, exhaustive matching of an open sum. See
-- @examples/Errors.hs@ for a full example.
decompose :: Sum (e ': es) b -> Either (Sum es b) (e b)
decompose sum@(Sum n v) = maybe (Left (Sum (n - 1) v)) Right (project sum)
{-# INLINE decompose #-}
-- | Special case of 'decompose' which knows that there is only one
-- possible type remaining in the @Sum@, @e@ thus it is guaranteed to
-- return @e@
decomposeLast :: Sum '[e] b -> e b
decomposeLast = either (error "Data.Sum: impossible case in decomposeLast") id . decompose
{-# INLINE decomposeLast #-}
-- | Add an arbitrary product @any@ to a product list @r@.
weaken :: Sum r w -> Sum (any ': r) w
weaken (Sum n v) = Sum (n+1) v
-- | @Element t r@ is a proof that @t@ is a member of @r@. This is implemented
-- in terms of @KnownNat@ rather than recursive typeclass lookups.
type (Element t r) = KnownNat (ElemIndex t r)
-- | An infix version of 'Element'. Note that you will need @-XTypeOperators@
-- turned on to use this.
type (t :< r) = Element t r
-- Find an index of an element in an `r'.
-- The element must exist, so this is essentially a compile-time computation.
elemNo :: forall t r . (t :< r) => P t r
elemNo = P (fromIntegral (natVal' (proxy# :: Proxy# (ElemIndex t r))))
-- | Helper to apply a function to a functor of the nth type in a type list.
-- An @Apply SomeClass fs@ instance means that @Sum fs@ has an instance of @SomeClass@.
-- Instances are written using 'apply' and an explicit type application:
--
-- > instance Apply SomeClass fs => SomeClass (Sum fs) where method = apply @SomeClass method
--
-- An @INLINEABLE@ pragma on such an instance may improve dispatch speed.
class Apply (c :: (* -> *) -> Constraint) (fs :: [* -> *]) where
apply :: (forall g . c g => g a -> b) -> Sum fs a -> b
apply' :: forall c fs a b . Apply c fs => (forall g . c g => (forall x. g x -> Sum fs x) -> g a -> b) -> Sum fs a -> b
apply' f u@(Sum n _) = apply @c (f (Sum n)) u
{-# INLINABLE apply' #-}
apply2 :: forall c fs a b d . Apply c fs => (forall g . c g => g a -> g b -> d) -> Sum fs a -> Sum fs b -> Maybe d
apply2 f u@(Sum n1 _) (Sum n2 r2)
| n1 == n2 = Just (apply @c (\ r1 -> f r1 (unsafeCoerce r2)) u)
| otherwise = Nothing
{-# INLINABLE apply2 #-}
apply2' :: forall c fs a b d . Apply c fs => (forall g . c g => (forall x. g x -> Sum fs x) -> g a -> g b -> d) -> Sum fs a -> Sum fs b -> Maybe d
apply2' f u@(Sum n1 _) (Sum n2 r2)
| n1 == n2 = Just (apply' @c (\ reinject r1 -> f reinject r1 (unsafeCoerce r2)) u)
| otherwise = Nothing
{-# INLINABLE apply2' #-}
pure (mkApplyInstance <$> [1..150])
instance Apply Foldable fs => Foldable (Sum fs) where
foldMap f = apply @Foldable (foldMap f)
{-# INLINABLE foldMap #-}
foldr combine seed = apply @Foldable (foldr combine seed)
{-# INLINABLE foldr #-}
foldl combine seed = apply @Foldable (foldl combine seed)
{-# INLINABLE foldl #-}
null = apply @Foldable null
{-# INLINABLE null #-}
length = apply @Foldable length
{-# INLINABLE length #-}
instance Apply Functor fs => Functor (Sum fs) where
fmap f = apply' @Functor (\ reinject a -> reinject (fmap f a))
{-# INLINABLE fmap #-}
(<$) v = apply' @Functor (\ reinject a -> reinject (v <$ a))
{-# INLINABLE (<$) #-}
instance (Apply Foldable fs, Apply Functor fs, Apply Traversable fs) => Traversable (Sum fs) where
traverse f = apply' @Traversable (\ reinject a -> reinject <$> traverse f a)
{-# INLINABLE traverse #-}
sequenceA = apply' @Traversable (\ reinject a -> reinject <$> sequenceA a)
{-# INLINABLE sequenceA #-}
instance Apply Eq1 fs => Eq1 (Sum fs) where
liftEq eq u1 u2 = fromMaybe False (apply2 @Eq1 (liftEq eq) u1 u2)
{-# INLINABLE liftEq #-}
instance (Apply Eq1 fs, Eq a) => Eq (Sum fs a) where
(==) = eq1
{-# INLINABLE (==) #-}
instance (Apply Eq1 fs, Apply Ord1 fs) => Ord1 (Sum fs) where
liftCompare compareA u1@(Sum n1 _) u2@(Sum n2 _) = fromMaybe (compare n1 n2) (apply2 @Ord1 (liftCompare compareA) u1 u2)
{-# INLINABLE liftCompare #-}
instance (Apply Eq1 fs, Apply Ord1 fs, Ord a) => Ord (Sum fs a) where
compare = compare1
{-# INLINABLE compare #-}
instance Apply Show1 fs => Show1 (Sum fs) where
liftShowsPrec sp sl d = apply @Show1 (liftShowsPrec sp sl d)
{-# INLINABLE liftShowsPrec #-}
instance (Apply Show1 fs, Show a) => Show (Sum fs a) where
showsPrec = showsPrec1
{-# INLINABLE showsPrec #-}
instance Apply Hashable1 fs => Hashable1 (Sum fs) where
liftHashWithSalt hashWithSalt' salt u@(Sum n _) = salt `hashWithSalt` apply @Hashable1 (liftHashWithSalt hashWithSalt' n) u
{-# INLINABLE liftHashWithSalt #-}
instance (Apply Hashable1 fs, Hashable a) => Hashable (Sum fs a) where
hashWithSalt = hashWithSalt1
{-# INLINABLE hashWithSalt #-}