Check whether a type is an instance of Show in Haskell?

前端 未结 3 1725
执笔经年
执笔经年 2020-11-30 10:48

Suppose I have a simple data type in Haskell for storing a value:

data V a = V a

I want to make V an instance of Show, regardless of a\'s t

相关标签:
3条回答
  • 2020-11-30 11:13

    Even if you could do this, it would be a bad design. I would recommend adding a Show constraint to a:

    instance Show a => Show (V a) where ...
    

    If you want to store members in a container data type that are not an instance of Show, then you should create a new data type fore them.

    0 讨论(0)
  • 2020-11-30 11:19

    As I said in a comment, the runtime objects allocated in memory don't have type tags in a Haskell program. There is therefore no universal instanceof operation like in, say, Java.

    It's also important to consider the implications of the following. In Haskell, to a first approximation (i.e., ignoring some fancy stuff that beginners shouldn't tackle too soon), all runtime function calls are monomorphic. I.e., the compiler knows, directly or indirectly, the monomorphic (non-generic) type of every function call in an executable program. Even though your V type's show function has a generic type:

    -- Specialized to `V a`
    show :: V a -> String  -- generic; has variable `a`
    

    ...you can't actually write a program that calls the function at runtime without, directly or indirectly, telling the compiler exactly what type a will be in every single call. So for example:

    -- Here you tell it directly that `a := Int`
    example1 = show (V (1 :: Int)) 
    
    -- Here you're not saying which type `a` is, but this just "puts off" 
    -- the decision—for `example2` to be called, *something* in the call
    -- graph will have to pick a monomorphic type for `a`.
    example2 :: a -> String
    example2 x = show (V x) ++ example1
    

    Seen in this light, hopefully you can spot the problem with what you're asking:

    instance Show (V a) where
        show (V a) = if a instanceof Show
                       then show a
                       else "Some Error."
    

    Basically, since the type for the a parameter will be known at compilation time for any actual call to your show function, there's no point to testing for this type at runtime—you can test for it at compilation time! Once you grasp this, you're led to Will Sewell's suggestion:

    -- No call to `show (V x)` will compile unless `x` is of a `Show` type.
    instance Show a => Show (V a) where ...
    

    EDIT: A more constructive answer perhaps might be this: your V type needs to be a tagged union of multiple cases. This does require using the GADTs extension:

    {-# LANGUAGE GADTs #-}
    
    -- This definition requires `GADTs`.  It has two constructors:
    data V a where
      -- The `Showable` constructor can only be used with `Show` types.
      Showable   :: Show a => a -> V a
      -- The `Unshowable` constructor can be used with any type.
      Unshowable :: a -> V a
    
    instance Show (V a) where
      show (Showable a) = show a
      show (Unshowable a) = "Some Error."
    

    But this isn't a runtime check of whether a type is a Show instance—your code is responsible for knowing at compilation time where the Showable constructor is to be used.

    0 讨论(0)
  • 2020-11-30 11:20

    You can with this library: https://github.com/mikeizbicki/ifcxt. Being able to call show on a value that may or may not have a Show instance is one of the first examples it gives. This is how you could adapt that for V a:

    {-# LANGUAGE FlexibleContexts #-}
    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE RankNTypes #-}
    {-# LANGUAGE TemplateHaskell #-}
    {-# LANGUAGE KindSignatures #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    {-# LANGUAGE UndecidableInstances #-}
    
    import IfCxt
    import Data.Typeable
    
    mkIfCxtInstances ''Show
    
    data V a = V a
    
    instance forall a. IfCxt (Show a) => Show (V a) where
        show (V a) = ifCxt (Proxy::Proxy (Show a))
            (show a)
            "<<unshowable>>"
    

    This is the essence of this library:

    class IfCxt cxt where
        ifCxt :: proxy cxt -> (cxt => a) -> a -> a
    
    instance {-# OVERLAPPABLE #-} IfCxt cxt where ifCxt _ t f = f
    

    I don't fully understand it, but this is how I think it works:

    It doesn't violate the "open world" assumption any more than

    instance {-# OVERLAPPABLE #-} Show a where
        show _ = "<<unshowable>>"
    

    does. The approach is actually pretty similar to that: adding a default case to fall back on for all types that do not have an instance in scope. However, it adds some indirection to not make a mess of the existing instances (and to allow different functions to specify different defaults). IfCxt works as a a "meta-class", a class on constraints, that indicates whether those instances exist, with a default case that indicates "false.":

    instance {-# OVERLAPPABLE #-} IfCxt cxt where ifCxt _ t f = f
    

    It uses TemplateHaskell to generate a long list of instances for that class:

    instance {-# OVERLAPS #-} IfCxt (Show Int) where ifCxt _ t f = t
    instance {-# OVERLAPS #-} IfCxt (Show Char) where ifCxt _ t f = t
    

    which also implies that any instances that were not in scope when mkIfCxtInstances was called will be considered non-existing.

    The proxy cxt argument is used to pass a Constraint to the function, the (cxt => a) argument (I had no idea RankNTypes allowed that) is an argument that can use the constraint cxt, but as long as that argument is unused, the constraint doesn't need to be solved. This is similar to:

    f :: (Show (a -> a) => a) -> a -> a
    f _ x = x
    

    The proxy argument supplies the constraint, then the IfCxt constraint is solved to either the t or f argument, if it's t then there is some IfCxt instance where this constraint is supplied which means it can be solved directly, if it's f then the constraint is never demanded so it gets dropped.


    This solution is imperfect (as new modules can define new Show instances which won't work unless it also calls mkIfCxtInstances), but being able to do that would violate the open world assumption.

    0 讨论(0)
提交回复
热议问题