-- | Generic computation of a skeleton. module Generics.SOP.Skeleton ( -- $skeleton Skeleton(..) ) where import Control.Exception import Data.Text (Text) import Generics.SOP -- $skeleton -- -- A skeleton for a record type has a defined "spine" but is undefined -- everywhere else. For instance, a skeleton for pairs would be -- -- > (undefined, undefined) -- | Generic computation of a skeleton. -- -- A skeleton for a record type has a defined "spine" but is undefined -- everywhere else. For instance, a skeleton for pairs would be -- -- > (undefined, undefined) -- -- We introduce a type class for this purpose because the skeleton for nested -- records would look like -- -- > (undefined, (undefined, undefined)) -- -- The default instance of 'skeleton' applies to record types; for everything -- else, use undefined (or error): -- -- > instance Skeleton SomeRecordType -- no where clause -- -- or -- -- > instance Skeleton SomeNonRecordType where skeleton = undefined -- -- This is an example of how SOP-style generic functions can -- be used with @DefaultSignatures@. -- -- Furthermore, metadata is used in order to produce better -- error messages. For the undefined components of a record, -- an error is triggered that mentions the name of the field. -- class Skeleton a where default skeleton :: (Generic a, HasDatatypeInfo a, Code a ~ '[xs], All Skeleton xs) => a -- | Returns a skeleton. skeleton :: a skeleton = gskeleton instance Skeleton [a] where skeleton = undefined instance Skeleton (Maybe a) where skeleton = undefined instance Skeleton Int where skeleton = undefined instance Skeleton Double where skeleton = undefined instance Skeleton Rational where skeleton = undefined instance Skeleton Bool where skeleton = undefined instance Skeleton Text where skeleton = undefined {------------------------------------------------------------------------------- Generic instance -------------------------------------------------------------------------------} -- | Compute a "spine" for single constructor datatype. That is, a valid of -- that type with a defined spine but undefined everywhere else. For record -- types we give "error" values that mention the names of the fields. gskeleton :: forall a xs. (Generic a, HasDatatypeInfo a, Code a ~ '[xs], All Skeleton xs) => a gskeleton = to $ gskeleton' (datatypeInfo (Proxy :: Proxy a)) gskeleton' :: All Skeleton xs => DatatypeInfo '[xs] -> SOP I '[xs] gskeleton' (ADT _ _ (c :* Nil)) = gskeletonFor c gskeleton' (Newtype _ _ c) = gskeletonFor c #if __GLASGOW_HASKELL__ < 800 gskeleton' _ = error "inaccessible" #endif gskeletonFor :: All Skeleton xs => ConstructorInfo xs -> SOP I '[xs] gskeletonFor (Constructor _) = SOP $ Z $ spineWithNames (hpure (K "")) gskeletonFor (Infix _ _ _) = SOP $ Z $ spineWithNames (hpure (K "")) gskeletonFor (Record _ fs) = SOP $ Z $ spineWithNames (hliftA sfieldName fs) where sfieldName :: FieldInfo a -> K String a sfieldName (FieldInfo n) = K n spineWithNames :: (All Skeleton xs, SListI xs) => NP (K String) xs -> NP I xs spineWithNames = hcliftA ps aux where aux :: Skeleton a => K String a -> I a aux (K "") = I $ skeleton aux (K n) = I $ mapException (addFieldName n) skeleton addFieldName :: FieldName -> ErrorCall -> ErrorCall addFieldName n (ErrorCall str) = ErrorCall (n ++ ": " ++ str) ps :: Proxy Skeleton ps = Proxy