首页 > 解决方案 > 在这种情况下如何防止类型范围?

问题描述

instance IsString Escaped where fromString a = Escaped a
instance ConvertibleStrings URI.URI String where convertString = show

data Escaped = forall a. ConvertibleStrings a String => Escaped a
instance Show Escaped where show (Escaped a) = cs a
class Escaping a where
    esc1 :: a -> Escaped
    esc2 :: a -> Escaped
instance ConvertibleStrings a String => Escaping a where
    esc1 a = Escaped $ escape1 $ cs a
    esc2 a = Escaped $ escape2 $ cs a

现在,OverloadedStrings启用扩展后,如果esc1esc2使用[sec1 ("a"::String), ...]. 如何防止它,所以只有一个[sec1 "a", ...]?可能吗?我尝试了 DS 扩展,但在这种情况下没有帮助。

标签: haskell

解决方案


我试过 DS 扩展

我假设您所说的“DS 扩展”是指DefaultSignatures。这使您可以在实例未实现该方法时为类型类方法指定默认签名。它通常用于Generic派生代码,而不是这样的情况。

如果您想指定字符串文字的默认类型,您可能需要使用default声明,它指定在解决模棱两可的类型时要搜索的类型。但是启用时的默认 default声明OverloadedStringsdefault (Integer, Double, String),因此String如果可能的话,应该已经选择了。如果您仍然有歧义,则有不同的原因,很可能是由ConvertibleStringorEscaping类引起的,它们没有内置的默认规则IsString

但是,这里有一个更简单的解决方案,可以从一开始就避免歧义。这个类型:

data Escaped
  = forall a. ConvertibleStrings a String
  => Escaped a

或与GADTs语法等效的:

data Escaped where
  Escaped :: (ConvertibleStrings a String) => a -> Escaped

是所谓的存在主义反模式的一个例子。它说Escaped包含某个隐藏类型的值,a该类型的实例为ConvertibleStrings a String. 但是该类的唯一方法是convertString,这里有a -> Stringhidden的类型a。这意味着您唯一可以对 an 做的事情Escaped就是将其用作String! String因此,仅存储 a并使用 a要简单得多,newtype因为只有一个字段:

newtype Escaped = Escaped String

由于懒惰,String除非使用它,否则不会对其进行评估,因此您无需担心。事实上,将 typeclass 约束存储在一个存在中比这稍微一些。

此外,这种模式通常会导致歧义:

class Escaping a where
    esc1 :: a -> Escaped
    esc2 :: a -> Escaped

instance ConvertibleStrings a String => Escaping a where
    esc1 a = Escaped $ escape1 $ cs a
    esc2 a = Escaped $ escape2 $ cs a

它说所有类型都是 的实例Escaping,因为“实例头”是Escaping a。实例上的任何约束,如ConvertibleStrings a String这里,仅在使用实例时在事后检查。

您可以尝试通过使用类型注释 ( cs :: a -> String) 或TypeApplications( cs @a @String) 约束类型来解决这些歧义,以使类型参数具体化,但这里更简单的解决方案是只制作需要约束的普通函数esc1,而不是类型类方法:esc2ConvertibleStrings

esc1 :: (ConvertibleStrings a String) => a -> Escaped
esc1 a = Escaped $ escape1 $ cs a

esc2 :: (ConvertibleStrings a String) => a -> Escaped
esc2 a = Escaped $ escape2 $ cs a

(同样,您可以对 a 做的唯一事情forall a. (ConvertibleStrings a String) => a是将其转换为 a String,因此这也等同于String -> Escaped,尽管在此函数中进行转换可能比在每个调用站点更方便。)

一般而言,具有单个参数的类型类表示一类型,具有多个参数的类型类ConvertibleStrings表示类型之间的关系函数ConvertibleStrings已经代表了你想要的类型关系;esc1并且esc2可以简单地使用这种关系。大多数时候,您可以(并且应该!)仅使用数据类型和函数来解决问题,而无需创建自己的类型类。


推荐阅读