Haskell catch违反了语义近似顺序?

Haskell catch违反了语义近似顺序?,haskell,Haskell,语义近似顺序表明,如果函数f是在其参数为非的情况下定义的,那么f在该参数中是常量(它不使用它)。但是考虑这个函数, import Control.Exception handleAll :: SomeException -> IO () handleAll e = putStrLn "caught" f :: String -> IO () f x = catch (putStrLn x) handleAll f undefined在GHCi中显示捕获的。但是,f在其参数中不是

语义近似顺序表明,如果函数
f
是在其参数为非的情况下定义的,那么
f
在该参数中是常量(它不使用它)。但是考虑这个函数,

import Control.Exception

handleAll :: SomeException -> IO ()
handleAll e = putStrLn "caught"

f :: String -> IO ()
f x = catch (putStrLn x) handleAll
f undefined
在GHCi中显示捕获的
。但是,
f
在其参数中不是常数,因为
f“test”
显示
test


某个地方有错误吗?

要正确地建模异常和捕获异常,您需要更丰富的术语指称语义,区分异常和非终止(以及区分不同的异常)。有关GHC实现的语义,请参见

请注意,这对Haskell的“纯片段”的指称语义没有影响,因为您无法观察纯代码中
IO a
值之间的区别(除了底部与非底部)

为了澄清我所说的Haskell的“纯片段”是什么意思,想象一下将
IO
类型定义为

data IO a = MkIO
catch
as

catch a h = MkIO
现在您的
f
没有问题,因为
f undefined
f“test”
都等于
MkIO
。从指称语义学的观点来看,这与解释相对应

[[
IO t
]={⊥ < ⊤}


由于我们可以对IO操作执行的唯一操作是
seq
ing它们并将它们组合到其他IO操作中,因此这是一种完全一致的指称语义,不会影响您谈论
length::[Bool]等语义的能力->Integer
。它对于理解执行IO操作时会发生什么是没有用的。但是,如果您想用指称语义来处理它,除了异常之外,您还会遇到许多困难。

没有错-这一原则只适用于纯函数。(请注意,由于
seq
和朋友的原因,这一点也不正确)。您之所以看到差异,是因为您正在执行
IO
操作,这在“技术上”仅在
main
中才可能-使用
IO
的所有其他操作都只会生成新的术语,或者,您无法观察
IO x
类型的术语的值。seq
并不违反此处讨论的原则。
catch
结果很难正确。事实上,在过去一两周里,我一直在努力修复相关的GHC错误。@dfeuer我认为
catch
有一个理论问题。库里·霍华德(Curry Howard)认为,bottom与数学错误有关。False是一个逻辑黑洞:对于任何命题
P
,False意味着
P
。一旦你进入底部,你就不能出来,你注定要说废话,就像在无限循环中发生的那样。
catch
打算将底部转换回定义的值,我看不出它如何正确地做。通过这种推理,
fx=\y->print(x,y)
不使用x?如果可以证明您所写的只有一个
ioa
(只有一个构造函数
MkIO
没有参数),编译器将优化
f
,强制它为常数,等于这个唯一的
ioa
。这不是发生的情况,正如GHCi中的执行所示。我从文章中了解到,GHC区分了几种底部:可以捕获的底部,其他的,如无限循环,不能捕获。我不知道根据Curry Howard的翻译,bottom与数学错误有关,只有一个。当然,我明确地说,这种指称语义(以非终结符标识的例外,IO被视为单例)不能帮助您预测执行IO操作时会发生什么。