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

问题描述 投票:0回答:1

我最近开始学习 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 型。

haskell types typeclass unit-type
1个回答
1
投票

已经有一个 默认类 得到了相当广泛的使用。这至少比你的

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
的东西。大多数时候,我认为拥有一个“具体地说”不产生信息的类型实际上比拥有一个多态类型更好,并且必须弄清楚它从哪里获取值以及它是否重要。对于最终用户来说,用“无信息”值替换 () 是一件微不足道的事情,但图书馆不可能以在所有可能情况下都有用的方式这样做。
    

© www.soinside.com 2019 - 2024. All rights reserved.