问题描述
模块Type.hs
定义了同音字词newtype
,并且仅导出其类型构造函数,而不导出值构造函数,以免暴露细节。它还提供了构造函数makeType
,以平衡缺少值ctor的情况。为什么需要将String
换成新类型?因为我希望它不只是String
;在我的特定情况下,Type
实际上被称为Line
,与makeType
相对应的内容强制其仅包含一个\n
作为最后一个字符。对我来说,newtype
似乎是最明显的选择。如果不是这种情况,请原谅我:我正在学习。
module Type (Type,makeType) where
newtype Type = Type String
makeType :: String -> Type
makeType = Type
为了以我喜欢的方式显示类型为Type
的值(例如,考虑到我的实际用例Line
,我可能想用漂亮的Unicode字符表示\n
,或使用序列<NL>
或其他任何内容),我创建了另一个模块TypeShow.hs
,后来我 (尝试执行我正在描述的内容)编辑了增加一些实用性。为什么要使用另一个模块?因为我猜想内部事物的工作方式以及在屏幕上显示的方式是两个不同的方面。我错了吗?
{-# LANGUAGE FlexibleInstances #-}
module TypeShow () where
import Type
instance Show Type where
show = const "Type"
-- the following instance came later,see below why
instance {-# OVERLAPS #-} Show (Maybe Type) where
show (Just t) = show t
show _ = ""
除了这对模块(描述了Type
的核心以及应如何显示)之外,我还创建了其他类似的对Type1
/ Type1Show
,Type2
/ Type2Show
,它们都包装了一个String
来表示和显示其他类似String
的实体。
由于其他原因,我还需要另一个包装可选值的类型,该类型可以是Type
,Type1
类型或任何其他类型,因此我编写了此模块
module Wrapper (Wrapper,makeWrapper,getInside) where
newtype Wrapper a = Wrapper { getInside :: Maybe a }
makeWrapper :: a -> Wrapper a
makeWrapper = Wrapper . Just
(实际上,Wrapper
实际上包装了多个Type
值,但是我会避免放置更多的细节,这是必要的;如果以下内容很愚蠢,因为我只包装了一个{{1 }}在Type
中的值,那么实际上请考虑它包裹了多个。)再次,在这里,我尝试隐藏Wrapper
的细节,同时提供Wrapper
来制作一个,并且makeWrapper
对其内部进行“受控”访问。
我也想在屏幕上显示此内容,因此我创建了一个相应的getInside
模块,以便WrapperShow.hs
的{{1}}方法依赖于内容的Wrapper
方法。
show
但是,在这一点上,当类型show
是module WrapperShow () where
import Wrapper
instance Show a => Show (Wrapper a) where
show = show . getInside
时,我想显示a
的内容,打印一个空字符串而不是Maybe Type
,或Wrapper
的内容;因此我写了我上面评论的nothing
。
鉴于此,Just
和instance Show (Maybe Type)
都正确显示为Type "hello"
,但Just $ Type "hello"
显示为Type
,就像使用{{1 }}是Wrapper $ Just $ Type "hello"
的原始实例,无论事实是,对于Just Type
(Maybe
)内的这种特定类型,我已经定制了Show
实例。 / p>
解决方法
在Show
的{{1}}实例声明中,我们真的不知道Wrapper
是什么。但是显然,我们必须已经选择要用于a
的{{1}}实例。使用可用的信息,唯一匹配的实例是默认实例Show
,不需要具体的Maybe a
。
GHC用户指南in the section about overlapping instances提到了“推迟”实例选择的概念:
这推迟了将哪个实例选择到呼叫站点的问题 对于f,到那时,人们对b类型的了解更多。你可以写这个 如果您使用FlexibleContexts扩展,请自己输入签名。
和
实例声明本身可能出现完全相同的情况[...]解决方案是通过在实例声明的上下文中添加约束来推迟选择
我们可以尝试这种技巧。除了要求Show a => Show (Maybe a)
,还要求整个 a
。现在Show a
实例被视为“给定”,我们不做本地决策。我认为这会延迟选择Show (Maybe a)
实例来调用像Show
这样的站点,在那里我们可以获得有关Show (Maybe a)
具体类型的更多信息。
检验这个假设:
print $ Wrapper $ Just $ Type 3
也就是说,我发现这种行为令人困惑,并且会在生产代码中避免这种情况。
正如@dfeuer在注释和链接的代码中指出的,重叠的实例是有问题的。例如,如果我们将此无辜的函数添加到此答案的代码中:
a
模块停止编译:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
newtype Type = Type Int
instance Show Type where
show = const "Type"
instance {-# OVERLAPS #-} Show (Maybe Type) where
show (Just t) = show t
show _ = ""
newtype Wrapper a = Wrapper (Maybe a)
instance Show (Maybe a) => Show (Wrapper a) where
show (Wrapper edit) = show edit
main :: IO ()
main = print $ Wrapper $ Just $ Type 3
-- output: Type
但是现在我很困惑。为什么原始问题中的实例定义foo :: Show a => Maybe a -> String
foo = show
不会发生完全相同的类型错误?
* Overlapping instances for Show (Maybe a)
arising from a use of `show'
Matching instances:
instance Show a => Show (Maybe a) -- Defined in `GHC.Show'
instance [overlap ok] Show (Maybe Type) -- Defined at Main.hs:14:27
(The choice depends on the instantiation of `a'
编译失败的原因似乎是实例搜索过程描述中的最后一个要点:
现在找到与以下对象统一的所有实例或作用域内给定的约束 目标约束,但不匹配。 [此处,显示(也许是类型)]这样的非候选对象 目标约束进一步扩大时,实例可能匹配 实例化。如果它们都是不连贯的顶级实例,则 搜索成功,返回主要候选对象。否则搜索 失败。 [...]
最后的项目符号(关于统一实例)使GHC 对于提交重叠的实例比较保守。例如:
f :: [b]-> [b]
假设从f的RHS中我们得到 约束C b [b]。但是GHC不会提交实例(C),因为 在f的特定调用中,b可能被实例化为Int,其中 案例实例(D)仍然更加具体。因此,GHC拒绝了 程序。
也许(与正常功能不同)实例定义不受此特定规则约束,但我看不到文档中提到的内容。