检查类型是否是Haskell中Show的实例?

时间:2016-03-03 23:30:07

标签: haskell

假设我在Haskell中有一个简单的数据类型用于存储值:

data V a = V a

我想让V成为Show的一个实例,无论其类型如何。如果a是Show的实例,则show (V a)应返回show a,否则应返回错误消息。或者在Pseudo-Haskell:

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

如何在Haskell中实现此行为?

3 个答案:

答案 0 :(得分:11)

正如我在评论中所说,在内存中分配的运行时对象在Haskell程序中没有类型标记。因此,没有像Java那样的通用instanceof操作。

考虑以下因素也很重要。在Haskell中,对于第一个近似(即,忽略一些初学者不应该过早解决的奇特东西),所有运行时函数调用都是单态。即,编译器直接或间接地知道可执行程序中的每个函数调用的单态(非泛型)类型。即使您的V类型的show函数具有泛型类型:

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

...你实际上无法编写一个在运行时调用该函数的程序,而无需直接或间接地告诉编译器每次调用中确切的a类型。例如:

-- 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

从这个角度来看,希望你可以用你所问的问题发现问题:

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

基本上,由于a参数的类型将在编译时为任何实际调用show函数而知道,因此在运行时测试此类型没有意义 - 您可以测试它在编译时!一旦你掌握了这一点,就会引起Will Sewell的建议:

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

编辑:更具建设性的答案可能是:您的V类型需要是多个案例的标记并集。这需要使用GADTs扩展程序:

{-# 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."

但这不是运行时检查类型是否为Show实例 - 您的代码负责在编译时知道要使用Showable构造函数。

答案 1 :(得分:9)

您可以使用此库:https://github.com/mikeizbicki/ifcxt。能够在可能有或没有show实例的值上调用Show是它给出的第一个示例之一。这就是你如何适应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>>"

这是这个库的精髓:

class IfCxt cxt where
    ifCxt :: proxy cxt -> (cxt => a) -> a -> a

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

我不完全理解它,但这是我认为它的工作原理:

它不会违反“开放世界”的假设

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

一样。该方法实际上非常类似于:为范围内没有实例的所有类型添加默认情况。但是,它增加了一些间接性,以免弄乱现有实例(并允许不同的函数指定不同的默认值)。 IfCxt作为一个“元类”,一个关于约束的类,指示这些实例是否存在,默认情况下指示“false”。

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

它使用TemplateHaskell为该类生成一长串实例:

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

这也意味着调用mkIfCxtInstances时不在范围内的任何实例都将被视为不存在。

proxy cxt参数用于将Constraint传递给函数,(cxt => a)参数(我不知道RankNTypes允许)是一个可以使用<的参数/ em>约束cxt,但只要该参数未使用,就不需要解决约束。这类似于:

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

proxy参数提供约束,然后IfCxt约束被解析为tf参数,如果它是t则存在提供此约束的某个IfCxt实例,这意味着它可以直接求解,如果它是f,则永远不需要约束,因此它会被删除。

此解决方案不完善(因为新模块可以定义新的Show实例,除非它还调用mkIfCxtInstances),否则无法工作,但能够执行 违反开放世界的假设。

答案 2 :(得分:2)

即使你能做到这一点,这也是一个糟糕的设计。我建议为Show添加a约束:

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

如果要将成员存储在不是Show实例的容器数据类型中,那么您应该在它们之前创建一个新的数据类型。