检查一个类型是否是Haskell中的Show实例?
假设我在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中如何实现?
正如我在评论中所述,在内存中分配的运行时对象在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
构造函数是要使用的运行时检查。
即使你可以这样做,这将是一个糟糕的设计。我会建议增加一个Show
约束a
:
instance Show a => Show (V a) where ...
如果要存储成员不在的Show
的实例的容器数据类型,那么你应该创建一个新的数据类型前他们。
您可以从这个库中: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
约束求解,无论是t
或f
说法,如果它是t
再有就是一些IfCxt
实例,其中该约束提供,这意味着它可以直接解决,如果它是f
那么约束从不要求,因此它被丢弃。
该解决方案是不完善的(如新的模块可以定义新Show
情况下,这是行不通的,除非它也要求mkIfCxtInstances
),但能够做到这一点会违反了开放世界的假设。
Haskell是一种完全删除类型的语言;在运行时,内存分配的结构不包含可用于恢复其类型或这些类型实现的类的标签。因此,没有Java或类似语言提供的'instanceof'运算符。 (有些更高级的技术可以在某些情况下用于类似的运行时类型反射,但如果您是初学者,您应该首先坚持基本知识!) –
此行为无法实现,因为哲学上_every类型是在每个班级。当然,如果编译器无法为某种类型的'Show'实例查找'show',则会出现错误;但这被理解为在概念上“你忘了写必要的实例”,而不是“你试图展示一个不可显示的类型”。类型类是开放的,任何人都可以稍后为某些库类定义实例。当编译库时,编译器不可能证明这不会发生! – leftaroundabout
也就是说:行为可以用[重叠实例]来模拟(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