假设我在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中实现此行为?
答案 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
约束被解析为t
或f
参数,如果它是t
则存在提供此约束的某个IfCxt
实例,这意味着它可以直接求解,如果它是f
,则永远不需要约束,因此它会被删除。
此解决方案不完善(因为新模块可以定义新的Show
实例,除非它还调用mkIfCxtInstances
),否则无法工作,但能够执行 违反开放世界的假设。
答案 2 :(得分:2)
即使你能做到这一点,这也是一个糟糕的设计。我建议为Show
添加a
约束:
instance Show a => Show (V a) where ...
如果要将成员存储在不是Show
实例的容器数据类型中,那么您应该在它们之前创建一个新的数据类型。