如何为单一类型专门化(重载)函数的行为?
我正在使用 Real World Haskell,并且作为练习的一部分,我意识到我想要一个类似于 的函数show :: Show(a) => a -> String,但它不影响字符串(而不是转义引号)。在伪代码中,我想要的是:
show' :: String -> String
show' = id
show':: Show(a) => a -> String
show' = show
显然这不起作用(ghc 抱怨多个类型签名和多个声明)。看来我应该改用类型类。当我尝试编写此代码时,编译器不断建议我添加更多语言扩展:
{-# LANGUAGE FlexibleInstances, UndecidableInstances, IncoherentInstances #-}
class Show' a where
show' :: a -> String
instance Show' String where
show' = id
instance (Show a) => Show' a where
show' = show
-- x == "abc"
x = show' "abc"
y = show' 9
起初,这似乎工作:x == "abc"和y == "9"预期。但是当我尝试在另一个多态函数中使用它时,编译器似乎总是将其解析为一般实现:
use :: (Show a) => a -> String
use x = show' x
-- z == ""abc""
z = use "abc"
所以我在这里做错了,我想知道是否有办法在没有一堆语言扩展的情况下做到这一点(其中一些似乎不是我应该鲁莽使用的东西)。如何show'使用现有show函数定义它?
回答
来自 OP 的评论:
[我] 很惊讶,这么简单的功能描述起来这么难实现。
这根本不是简单的函数,因为它在某种程度上打破了基于类型类的多态性的通常保证。当我们看到一个实例
instance Show' a => Show' [a] where ...
我们通常假设此实例适用于所有列表类型,没有例外。对类型的普遍量化a真的意味着forall a.
重叠/不连贯的实例打破了这个假设,并允许像你这样的“特殊情况”,正如大卫在他的回答中指出的那样。这是一种可能的解决方案,但请注意,通过打破上述假设,重叠/不连贯的实例会使实例解析变得相当脆弱,有时会意外地导致错误的实例被采用。
作为替代方案,您可以考虑利用Typeable我认为争议较小的类型类:
import Data.Typeable
show' :: (Show a, Typeable a) => a -> String
show' x = case cast x of
Just s -> s -- cast succeeded, it is a string
Nothing -> show x -- case failed, it is not a string
请注意Typeable,在类型中, 的存在表明该函数是使用临时多态定义的,允许我们检查是否a是这样和这样的类型,这在参数多态下通常是不可能的。
请注意,这并不需要IncoherentInstances也没有OverlappingInstances。