Skip to content

Instantly share code, notes, and snippets.

Last active May 5, 2020 15:21
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save nh2/1a03b7873dbed348ef64fe536028776d to your computer and use it in GitHub Desktop.
Getting the name of a type with GHC.Generics
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
module GenericsTypeName
( TypeName(..)
, readBS
) where
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BS8
import Data.Proxy
import GHC.Generics
import Text.Read (readMaybe)
-- | A class of types from which we can get the name as a string.
-- This class can be automatically derived, e.g. with
-- `deriving instance TypeName MyType`.
class TypeName a where
typename :: Proxy a -> String
default typename :: (Generic a, GTypeName (Rep a)) => Proxy a -> String
typename _proxy = gtypename (from (undefined :: a))
-- | Generic equivalent to `TypeName`.
class GTypeName f where
gtypename :: f a -> String
instance (Datatype c) => GTypeName (M1 i c f) where
gtypename m = datatypeName m
-- | Tries to parse a `ByteString` into a constructor of type `a`, failing
-- with a nice error message if that failed.
-- Note: This should have a `MonadFail` instad of `Monad` constraint, once that exists in base.
readBS :: forall m a . (Monad m, TypeName a, Read a) => ByteString -> m a
readBS bs = case readMaybe (BS8.unpack bs) of
Nothing -> fail $ "The string " ++ show bs
++ " could not be turned into a constructor of type "
++ typename (Proxy :: Proxy a)
Just x -> return x
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment