我最近开始学习 Haskell,我编写了以下代码作为小型解析库的一部分:
-- Successful iff the input string has a length of zero
parseEOF :: Parser ()
parseEOF = Parser p
where
p [] = Just ((), "")
p _ = Nothing
此代码将受益于多态性,因为它的
()
方面仅表明输出中没有表达任何信息,这对于任何非 Void
类型都是可能的,例如对于整数,这个“无相关信息”元素可以用 0 来标识。
解决此问题的一个简单方法是创建以下类型类:
class NonVoid n where
nil :: n
上面的代码重新呈现为
parseEOF :: NonVoid n => Parser n
parseEOF = Parser p
where
p [] = Just (nil, "")
p _ = Nothing
类型类方法的问题是对所有类型实现起来可能很麻烦。是否有另一种方法来表达
()
是 Haskell 中所有其他类型的“子集”的概念?我不只是要求提供上面的代码(我的问题可以通过其他方式解决),但这似乎是一个非常重要的想法。这个想法可以进一步推广到 2 型和任何 n 型。
已经有一个 默认类 得到了相当广泛的使用。这至少比你的
NonVoid
有优势,因为已经有很多实例了。
我发现它没有你想象的那么有用,因为你想要作为类型默认值的which值通常是特定于上下文的,但这可能是最接近选择任意值的规范方法多种类型的价值。
或者,您可以使用泛型来选择具有
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
的东西。大多数时候,我认为拥有一个“具体地说”不产生信息的类型实际上比拥有一个多态类型更好,并且必须弄清楚它从哪里获取值以及它是否重要。对于最终用户来说,用“无信息”值替换 ()
是一件微不足道的事情,但图书馆不可能以在所有可能情况下都有用的方式这样做。