1

模块Type.hs定义了谐音newtype,只导出其类型构造函数,不导出值构造函数,避免暴露细节;它还提供了一个构造函数makeType来平衡缺少值ctor。为什么我需要将 a 包装成String一个新类型?因为我希望它不仅仅是一个String; 在我的具体情况下,Type实际上被称为Line,并且对应于makeType强制它只包含一个\n,作为最后一个字符。Anewtype对我来说似乎是最明显的选择。如果不是这样,请原谅我:我正在学习。

module Type (Type, makeType) where

newtype Type = Type String

makeType :: String -> Type
makeType = Type

为了以Type我喜欢的方式显示类型的值(例如,给定我的实际用例Line,我可能想\n用一个漂亮的 unicode 字符或序列<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/ Type1ShowType2/ Type2Show,它们都包裹 aString来表示和显示其他类似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值,但我会避免提供更多必要的细节;如果以下内容很愚蠢,因为我只在 中包装了一个TypeWrapper,那么请考虑它实际上包装了多个值。)同样,在这里我试图隐藏细节,Wrapper同时提供makeWrapper制作一个,并getInside对其内部进行“受控”访问。

我也想在屏幕上显示这个,所以我创建了一个相应的WrapperShow.hs模块,所以它Wrappershow方法依赖于内容的show方法。

module WrapperShow () where

import Wrapper

instance Show a => Show (Wrapper a) where
  show = show . getInside

然而,此时,当类型a为 a时Maybe Type,我想显示Wrapper打印空字符串的内容,而不是Nothing,或 ; 的内容Just。因此,我写了instance Show (Maybe Type)我在上面评论过的内容。

鉴于此,Type "hello"andJust $ Type "hello"都正确显示为Type,但Wrapper $ Just $ Type "hello"显示为Just Type,就像它使用Maybe的原始实例一样,无论对于( )Show中的这种特定类型,我已经自定义了实例。MaybeTypeShow

4

1 回答 1

1

在 的Show实例声明中Wrapper,我们真的不知道是什么a。但显然我们必须已经ShowMaybe a. 有了可用的信息,唯一匹配的实例是默认实例Show a => Show (Maybe a),它不需要具体的a.

GHC 用户指南,在关于重叠实例的部分中,提到了“推迟”实例选择的概念:

这推迟了选择哪个实例到 f 的调用站点的问题,到那时我们对类型 b 的了解更多。如果您使用 FlexibleContexts 扩展,您可以自己编写此类型签名。

实例声明本身也可能出现完全相同的情况 [...] 解决方案是通过将约束添加到实例声明的上下文中来推迟选择

我们可以试试这样的伎俩。与其要求Show a,不如要求整体 Show (Maybe a)。现在该Show实例被视为“给定”,我们不做出本地决定。我认为这会延迟选择Show (Maybe a)调用站点的实例,例如print $ Wrapper $ Just $ Type 3,我们有更多关于具体类型的信息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

也就是说,我发现这种行为令人困惑,并且会在生产代码中避开它。


正如@dfeuer 在评论和链接代码中指出的那样,重叠实例是有问题的。例如,如果我们将这个无辜的函数添加到这个答案的代码中:

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'

但现在我很困惑。Show (Wrapper a)为什么原始问题中的实例定义不会发生完全相同的类型错误?


无法编译的原因foo似乎是实例搜索过程描述中的最后一个要点:

现在找到与目标约束统一但不匹配的所有实例或范围内给定约束。[here, Show (Maybe Type)]当目标约束被进一步实例化时,这样的非候选实例可能会匹配。如果它们都是不连贯的顶级实例,则搜索成功,返回主要候选者。否则搜索失败。[...]

最后一个项目符号(关于统一实例)使 GHC 对提交重叠实例持保守态度。例如:

f :: [b] -> [b]

假设从 f 的 RHS 我们得到约束 C b [b]。但是 GHC 不承诺实例 (C),因为在特定的 f 调用中,b 可能会被实例化为 Int,在这种情况下实例 ​​(D) 将更加具体。所以 GHC 拒绝了这个计划。

也许——与普通函数不同——实例定义不受此特定规则的约束,但我没有看到文档中提到这一点。

于 2020-10-06T11:43:14.303 回答