I'd like to write a horribly non-parametric version of a function of type
pretty :: (Show a) => a -> Text
such that
pretty :: Text -> Text = id
pretty :: String -> Text = T.pack
pretty :: (Show a) => a -> Text = T.pack . show
So the idea is that anything that already has a Show
instance can be turned into a "pretty" Text
by just show
-ing it, except for Text
and String
which we want to special-case.
The following code works:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, FlexibleContexts #-}
{-# LANGUAGE DataKinds, ConstraintKinds #-}
module Pretty (pretty) where
import Data.Text (Text)
import qualified Data.Text as T
type family StringLike a :: Bool where
StringLike String = True
StringLike Text = True
StringLike a = False
class (b ~ StringLike a) => Pretty' a b where
pretty' :: a -> Text
instance Pretty' String True where
pretty' = T.pack
instance Pretty' Text True where
pretty' = id
instance (Show a, StringLike a ~ False) => Pretty' a False where
pretty' = T.pack . show
type Pretty a = (Pretty' a (StringLike a))
pretty :: (Pretty a) => a -> Text
pretty = pretty'
and it can be used without exporting anything except the pretty
function.
However, I am not too happy about the type signature for pretty
:
pretty :: (Pretty a) => a -> Text
I feel that since StringLike
is a closed type family, there should be a way for GHC to figure out that if only (Show a)
holds, (Pretty a)
is already satisfied, since:
1. The following hold trivially just by substituting the results of applying StringLike:
(StringLike String ~ True, Pretty' String True)
(StringLike Text ~ True, Pretty' Text True)
2. For everything else, we *also* know the result of applying StringLike:
(Show a, StringLike a ~ False) => (Pretty' a (StringLike a))
Is there a way to convince GHC of this?
Prelude
qualified, and renamePretty
toShow
... – CactusStringLike
yields a type of kind(-lifted)Bool
,Show
yields a type of kindConstraint
. It's not just that GHC doesn't understand the relationship; they actually follow different laws of logic. With liftedBool
, you assume the law of the excluded middle, but withConstraint
, you may not rely on that. – user824425