tohava
tohava

Reputation: 5412

Generically finding out whether the type of a value belongs to a type class or not

I want to write the isShowable function as part of this code.

data MaybeShowable = forall a . Show a => Showable a | Opaque
f :: (Data d) => d -> String
f x = case isShowable x of
        Showable s -> show s
        Opaque -> "<<OPAQUE>>"
isShowable :: (Data d) => d -> MaybeShowable
isShowable = ???

Is this possible by using the Data instance? If not, what is the best way to do it?

Note: If there's no other option, I'm willing to settle for this a version that works only for type class instances visible through the imports to the module in which isShowable is defined.

Upvotes: 3

Views: 139

Answers (2)

phadej
phadej

Reputation: 12123

I'm not sure what is your real intention is, but it looks like you'd like to embed Java idiom into Haskell.

As mentioned in other SO question what you are doing is going to turn into anti-pattern.

You have added a clarification:

If I would be willing to settle for "typeclass instances visible through the imports to the module in which isShowable is defined".

Why wouldn't you wrap your type in:

data MaybeShowable a where
  Showable :: forall b. Show b => b -> MaybeShowable b
  Opaque   :: forall b.           b -> MaybeShowable b

instance Show (MaybeShowable a) where
  show (Showable x) = show x
  show (Opaque x)   = "<<OPAQUE>>"

And have your functions operate on MaybeShowable a, instead of plain a.

Yet this is ugly still. Isn't it be easier to operate directly on Show a => a, or a.

Other way is to capture Show a dictionary early enough, i.e. have data type:

data MaybeShowable a = Showable a String -- Or even Showable a (a -> String)
                     | Opaque a

instance Show (MaybeShowable a) where
  show (Showable x s) = s
  show (Opaque x)     = "<<OPAQUE>>"

wrapShow :: Show a => a -> MaybeShowable a
wrapShow x = Showable x (show x) -- Showable x show

wrapOpaque :: a -> MaybeShowable a
wrapOpaque = Opaque

The variation of this approach is used in e.g. QuickCheck's forAll. That part is Haskell98. There the show x is closed over into closure, which maybe executed or not. Lazyness is the key point here!

Upvotes: 4

aavogt
aavogt

Reputation: 1308

You can ask using template haskell which instances are available:

    module IsInstance where

    import Language.Haskell.TH; import Data.Typeable
    import Data.Generics; import Data.Monoid

    -- $(isInst ''Show) :: Typeable a => a -> Bool
    isInst :: Name -> ExpQ
    isInst className = do
        ClassI _ insts <- reify className
        ClassI _ typeableInsts <- reify ''Typeable
        let typeOfs = [ [| typeRep (Proxy :: Proxy $(return ty)) |]
                            | InstanceD _ (AppT _ ty) _ <- insts,
                              hasNoVarT ty,
                              or [ ty_ == ty | InstanceD _ (AppT _ ty_) _ <- typeableInsts ] ]
        [| \ val -> typeOf val `elem` $(listE typeOfs) |]

    hasNoVarT xs = getAll $ everything
        (<>)
        (mkQ mempty (\ x -> case x of
                      VarT {} -> All False
                      _ -> mempty))
        xs

$(isInst ''Show) (1 :: Int) is true, but unfortunately $(isInst ''Show) (1 :: Rational) is false, since here using == doesn't say that an instance for Show (Ratio a) can be used with type Rational = Ratio Integer. So a complete solution is going to have to know how instances are selected.

Upvotes: 2

Related Questions