Data.Void.absurd与different有什么不同?

时间:2016-07-24 20:35:10

标签: haskell

我今天早些时候看过Inverse of the absurd function,虽然我很清楚drusba :: a -> Void的任何可能实现都不会终止(毕竟,构建Void是不可能的),我不知道理解为什么absurd :: Void -> a的情况并非如此。考虑GHC的实施:

newtype Void = Void Void

absurd :: Void -> a
absurd a = a `seq` spin a where
   spin (Void b) = spin b
在我看来,

spin无休止地解开了无限系列的Void newtype包装器,即使你找到Void传递它也永远不会返回。无法区分的实现类似于:

absurd :: Void -> a
absurd a = a `seq` undefined

鉴于此,为什么我们说absurd是一个值得存在于Data.Void中的正确函数,但是

drusba :: a -> Void
drusba = undefined

是一个无法定义的函数?它是否像以下那样?

  

absurd是一个总函数,为其(空)域中的任何输入提供非底部结果,而drusba是部分的,给出其中一些(实际上所有)输入的最低结果域。

2 个答案:

答案 0 :(得分:19)

由于历史原因,任何Haskell数据类型(包括newtype)必须至少有一个构造函数。

因此,要在" Haskell98"中定义Void。一个人需要依赖类型级递归newtype Void = Void Void。此类型没有(非底部)值。

absurd函数必须依赖(值级别)递归来处理"怪异的" Void类型的形式。

在更现代的Haskell中,通过一些GHC扩展,我们可以定义零构造函数数据类型,这将导致更清晰的定义。

{-# LANGUAGE EmptyDataDecls, EmptyCase #-}
data Void
absurd :: Void -> a
absurd x = case x of { }    -- empty case

这个案例是详尽无遗的 - 它确实处理了Void的所有构造函数,它们都是零。因此它是完全的。

在其他一些功能语言中,例如Agda或Coq,上面案例的变体在处理像Void这样的空类型时是惯用的。

答案 1 :(得分:17)

Data.Void已从void包移至基本版base中的4.8(GHC 7.10)。如果您查看void的Cabal文件,您会看到旧版Data.Void版仅包含baseNow, Void is defined as chi suggests

data Void

absurd :: Void -> a
absurd a = case a of {}

完全有效。

我认为旧定义背后的想法是这样的:

考虑类型

data BadVoid = BadVoid BadVoid

此类型无法完成工作,因为实际上可以使用该类型定义非底部(coinductive)值:

badVoid = BadVoid badVoid

我们可以通过使用严格注释来解决这个问题,严格注释(至少有点)强制类型是归纳的:

data Void = Void !Void

在可能会或可能不会实际持有,但至少在道德上持有的假设下,我们可以合理地对任何归纳类型执行归纳。所以

spin (Void x) = spin x
如果假设我们有Void类型的东西,

将永远终止。

最后一步是用newtype替换single-strict-constructor数据类型:

newtype Void = Void Void

这总是合法的。然而,由于spindata之间的模式匹配语义不同,newtype的定义已经改变了含义。为了准确地保留含义,spin应该写成

spin !x = case x of Void x' -> spin x'

(避免spin !(Void x)绕过newtype构造函数和爆炸模式之间的交互中现在修复的错误;但是对于GHC 7.10(ha!),这种形式实际上并不会产生所需的错误消息,因为它是“优化的” “进入无限循环”,此时absurd = spin

值得庆幸的是,它最终并不重要,因为整个旧定义有点愚蠢。