Skip to content

Commit c1040b1

Browse files
committed
Add IsSumType
This is handy for the common case where a datatype is just a tagged union of sub-types. Many instance declarations for such types just consist of dispatching to the instance method for the sub-type. These can be written generically with `IsSumType` and deriving-via.
1 parent 701bac9 commit c1040b1

File tree

4 files changed

+89
-1
lines changed

4 files changed

+89
-1
lines changed

generics-sop/src/Generics/SOP.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ module Generics.SOP (
227227
, ProductCode
228228
, productTypeFrom
229229
, productTypeTo
230+
, IsSumType
231+
, SumCode
232+
, sumTypeFrom
233+
, sumTypeTo
230234
, IsEnumType
231235
, enumTypeFrom
232236
, enumTypeTo

generics-sop/src/Generics/SOP/Universe.hs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,47 @@ productTypeTo :: IsProductType a xs => NP I xs -> a
185185
productTypeTo = to . SOP . Z
186186
{-# INLINE productTypeTo #-}
187187

188+
-- | Constraint that captures that a datatype is a (simple) sum type,
189+
-- i.e., a type with some number of constructors, each of which
190+
-- has a single argument.
191+
--
192+
-- It also gives access to the list of types which make up the union.
193+
--
194+
-- @since 0.5.2.0
195+
--
196+
type IsSumType (a :: Type) (xs :: [Type]) =
197+
(Generic a, AllZip IsSingletonOf xs (Code a))
198+
199+
-- | Direct access to the list of types that makes up a sum type.
200+
--
201+
-- @since 0.5.2.0
202+
--
203+
type SumCode (a :: Type) = Heads (Code a)
204+
205+
-- | Convert from a sum type to its sum representation.
206+
--
207+
-- @since 0.5.2.0
208+
--
209+
sumTypeTo :: IsSumType a xs => a -> NS I xs
210+
sumTypeTo = go . unSOP . from
211+
where
212+
go :: AllZip IsSingletonOf xs xss => NS (NP I) xss -> NS I xs
213+
go (Z (x :* Nil)) = Z x
214+
go (S xss) = S $ go xss
215+
{-# INLINE sumTypeTo #-}
216+
217+
-- | Convert a sum representation to the original type.
218+
--
219+
-- @since 0.5.2.0
220+
--
221+
sumTypeFrom :: IsSumType a xs => NS I xs -> a
222+
sumTypeFrom = to . SOP . go
223+
where
224+
go :: AllZip IsSingletonOf xs xss => NS I xs -> NS (NP I) xss
225+
go (Z x) = Z (x :* Nil)
226+
go (S xss) = S $ go xss
227+
{-# INLINE sumTypeFrom #-}
228+
188229
-- | Constraint that captures that a datatype is an enumeration type,
189230
-- i.e., none of the constructors have any arguments.
190231
--
@@ -201,7 +242,7 @@ enumTypeFrom :: IsEnumType a => a -> NS (K ()) (Code a)
201242
enumTypeFrom = map_NS (const (K ())) . unSOP . from
202243
{-# INLINE enumTypeFrom #-}
203244

204-
-- | Convert a sum representation to ihe original type.
245+
-- | Convert a enum representation to ihe original type.
205246
--
206247
enumTypeTo :: IsEnumType a => NS (K ()) (Code a) -> a
207248
enumTypeTo = to . SOP . cmap_NS (Proxy :: Proxy ((~) '[])) (const Nil)

generics-sop/test/Example.hs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
{-# LANGUAGE TypeFamilies #-}
99
{-# LANGUAGE FlexibleContexts #-}
1010
{-# LANGUAGE PolyKinds #-}
11+
{-# LANGUAGE StandaloneDeriving #-}
12+
{-# LANGUAGE DerivingVia #-}
13+
{-# LANGUAGE DeriveAnyClass #-}
14+
{-# LANGUAGE DerivingStrategies #-}
15+
{-# LANGUAGE UndecidableInstances #-}
16+
{-# LANGUAGE TypeApplications #-}
17+
{-# LANGUAGE ScopedTypeVariables #-}
1118
{-# OPTIONS_GHC -fno-warn-deprecations #-}
1219
module Main (main, toTreeC, toDataFamC) where
1320

@@ -206,6 +213,22 @@ instance Enumerable ABCC where
206213
instance Enumerable VoidC where
207214
enum = fmap toVoidC genumS
208215

216+
-- Use with derving via: much better than trying to write an overlapping
217+
-- `(xs ~ SumCode a, IsSumType a xs, All Show xs) => Show a` instance
218+
newtype AsSum a = AsSum a
219+
instance (xs ~ SumCode a, IsSumType a xs, All Show xs) => Show (AsSum a) where
220+
show (AsSum a) = go @xs $ sumTypeTo a
221+
where
222+
go :: (All Show xs') => NS I xs' -> String
223+
go (Z (I x)) = show x
224+
go (S xss) = go xss
225+
226+
data UnionType = C1 Tree | C2 TreeB
227+
deriving stock (GHC.Generic)
228+
-- Use anyclass deriving via GHC generics to fit this all in one deriving clause
229+
deriving anyclass (Generic)
230+
deriving Show via (AsSum UnionType)
231+
209232
-- Tests
210233
main :: IO ()
211234
main = do
@@ -238,3 +261,5 @@ main = do
238261
print (voidDatatypeInfo == demotedVoidDatatypeInfo)
239262
print (dataFamDatatypeInfo == demotedDataFamDatatypeInfo)
240263
print $ convertFull tree
264+
print $ C1 $ Leaf 1
265+
print $ C2 $ LeafB 2

sop-core/src/Data/SOP/Constraint.hs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,16 @@ type family
210210
type family Head (xs :: [a]) :: a where
211211
Head (x ': xs) = x
212212

213+
-- We can't do this with a 'Map' family and 'Head' without unsaturated type families.
214+
215+
-- | Utility function to compute the heads of a type-level lists of type-level lists.
216+
--
217+
-- @since 0.5.2.0
218+
--
219+
type family Heads (xss :: [[k]]) :: [k] where
220+
Heads '[] = '[]
221+
Heads (x ': xs) = Head x ': Heads xs
222+
213223
-- | Utility function to compute the tail of a type-level list.
214224
--
215225
-- @since 0.3.1.0
@@ -284,3 +294,11 @@ type family AllZipN (h :: (k -> Type) -> (l -> Type)) (c :: k1 -> k2 -> Constrai
284294
-- on whether the argument is indexed by a list or a list of lists.
285295
--
286296
type family SListIN (h :: (k -> Type) -> (l -> Type)) :: l -> Constraint
297+
298+
-- | Constraint that captures that a type-level list is a singleton of the given element.
299+
--
300+
-- This is a class rather than a type synonym so it can be passed as a type argument to types that take
301+
-- a constraint, such as 'AllZip'.
302+
--
303+
class (as ~ '[a]) => IsSingletonOf (a :: k) (as :: [k])
304+
instance (as ~ '[a]) => IsSingletonOf (a :: k) (as :: [k])

0 commit comments

Comments
 (0)