2016-03-03 90 views
6

假设我在Haskell有一个简单的数据类型存储的值:检查一个类型是否是Haskell中的Show实例?

data V a = V a 

我想使V展会的一个实例,不管的类型。如果a是Show的一个实例,则show (V a)应返回show a,否则应返回错误消息。或者在伪Haskell中:

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

这种行为在Haskell中如何实现?

+4

Haskell是一种完全删除类型的语言;在运行时,内存分配的结构不包含可用于恢复其类型或这些类型实现的类的标签。因此,没有Java或类似语言提供的'instanceof'运算符。 (有些更高级的技术可以在某些情况下用于类似的运行时类型反射,但如果您是初学者,您应该首先坚持基本知识!) –

+7

此行为无法实现,因为哲学上_every类型是在每个班级。当然,如果编译器无法为某种类型的'Show'实例查找'show',则会出现错误;但这被理解为在概念上“你忘了写必要的实例”,而不是“你试图展示一个不可显示的类型”。类型类是开放的,任何人都可以稍后为某些库类定义实例。当编译库时,编译器不可能证明这不会发生! – leftaroundabout

+0

也就是说:行为可以用[重叠实例]来模拟(https://downloads.haskell.org/~ghc/7.8.1/docs/html/users_guide/type-class-extensions.html#instance-overlap) ,这被认为有点丑陋(甚至不安全)。也许更好地匹配这个想法是[封闭类型家族](http://research.microsoft.com/en-us/um/people/simonpj/papers/ext-f/axioms-extended.pdf),虽然他们不很容易让你实现这个show实例。 – leftaroundabout

回答

10

正如我在评论中所述,在内存中分配的运行时对象在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函数的时间,都没有必要在运行时测试这种类型 - 您可以在编译时测试它!一旦你掌握这个,你就导致了威尔休厄尔的建议:

-- 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构造函数是要使用的运行时检查。

+0

如果出于某种原因,您真的*想要为非显示类型返回错误信息,该怎么办? – immibis

+0

@immibis:现在我想起来了,你可以使用带Show''约束的最新版本,[编译器选项将错误类型推迟到运行时](https://downloads.haskell.org/~ghc /latest/docs/html/users_guide/defer-type-errors.html)。但是编译器仍然会警告你,你的程序在运行时可能会失败 - 它甚至会告诉你究竟哪一行会失败! –

+0

*返回*错误消息,不会使程序崩溃。 – immibis

2

即使你可以这样做,这将是一个糟糕的设计。我会建议增加一个Show约束a

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

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

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作品AA“元级”,对限制类,指示这些实例是否存在,与表示默认情况下“假”:

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允许这)是一个参数,可以使用约束cxt,但只要这样的说法是不使用的,约束不需要解决。这是类似的:

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

proxy参数提供的约束,那么IfCxt约束求解,无论是tf说法,如果它是t再有就是一些IfCxt实例,其中该约束提供,这意味着它可以直接解决,如果它是f那么约束从不要求,因此它被丢弃。


该解决方案是不完善的(如新的模块可以定义新Show情况下,这是行不通的,除非它也要求mkIfCxtInstances),但能够做到这一点违反了开放世界的假设。

相关问题