如何表达 Haskell 中“()”是所有其他(非“Void”)类型的子集这一事实?

Wil*_*man 3 haskell types typeclass unit-type

我最近开始学习 Haskell,我编写了以下代码作为小型解析库的一部分:

-- Successful iff the input string has a length of zero
parseEOF :: Parser ()
parseEOF = Parser p
  where
    p [] = Just ((), "")
    p _ = Nothing
Run Code Online (Sandbox Code Playgroud)

该代码将受益于多态性,因为()它只是表明输出中没有表达任何信息,这对于任何非类型都是可能的Void,例如对于整数,这个“无相对信息”元素可以用 0 标识。

解决此问题的一个简单方法是创建以下类型类:

class NonVoid n where
  nil :: n
Run Code Online (Sandbox Code Playgroud)

上面的代码重新呈现为

parseEOF :: NonVoid n => Parser n
parseEOF = Parser p
  where
    p [] = Just (nil, "")
    p _ = Nothing
Run Code Online (Sandbox Code Playgroud)

类型类方法的问题是对所有类型实现起来可能很麻烦。()是否有另一种方法来表达Haskell 中所有其他类型的“子集”的概念?我不只是要求提供上面的代码(我的问题可以通过其他方式解决),但这似乎是一个非常重要的想法。这个想法可以进一步推广到 2 型和任何 n 型。

Ben*_*Ben 10

已经有一个相当广泛使用的默认类。这至少比你的有优势NonVoid,因为它已经有很多实例了。

我发现它没有你想象的那么有用,因为你希望哪个值成为类型的默认值通常是特定于上下文的,但它可能是最接近选择多个任意值的规范方法类型。

或者,您可以使用泛型来选择实例的任何类型的任意值Generic。同样,您仍然依赖于您想要使用的每种类型的现有实例,但其中很多已经存在并且Generic可以派生,因此它并不是非常繁重。

然而,最终,我认为将其推广到任何有人居住的类型实际上并不是一个好主意()。如果我看到了,parseEOF :: Parser ()只需阅读名称和类型,我就会确切地知道它的作用。如果我看到的话,parseEOF :: Default a => Parser a我会相当惊讶,并且在不阅读文档或深入研究源代码的情况下不太相信它的行为。我认为Parser ()这是一种更好的类型来表达“只有在没有输入可供使用、不产生任何信息的情况下解析器才会成功”的想法。从信息论的角度来看,它是每个居住类型的子集,这一事实似乎对于生成易于使用和理解的界面()没有用处。

事实上,广义的parseEOF :: Default a => Parse a使用起来可能非常不方便,因为大多数时候用户不会对结果值做任何事情。这意味着 GHC 没有信息可用于推断它必须解析Default实例的类型(或您使用的任何类),这意味着您将收到歧义错误。它只是使您必须在常见情况下添加类型注释(或类型应用程序),当您希望它生成时,()因为您不需要其他值。

这同样适用于大量现有的单子操作(()其结果类型)。您的逻辑并不特定于解析器或 EOF;同样的推理可能表明putStrLn应该推广到类似的东西putStrLn :: Default a => String -> IO a。大多数时候,我认为拥有一个明确表示它不产生任何信息的类型实际上比拥有一个多态类型更好,并且必须弄清楚它从哪里获取值以及它是否重要。对于最终用户来说,当他们愿意时,用“无信息”值替换()是一件微不足道的事情,但图书馆不可能以在所有可能情况下都有用的方式这样做。