今天早上我注意到一些有趣的事情,我想询问一下它是否有重要意义。
所以在Haskell中,undefined在语义上包含非终止。所以应该不可能有一个功能
isUndefined :: a -> Bool
因为语义表明这解决了暂停问题。
但是我相信GHC的一些内置功能允许“相当可靠”地破坏这种限制。特别是赶上#。
以下代码允许“非常可靠地”检测到未定义的值:
import Control.Exception
import System.IO.Unsafe
import Unsafe.Coerce
isUndefined :: a -> Bool
isUndefined x = unsafePerformIO $ catch ((unsafeCoerce x :: IO ()) >> return False) ((\e -> return $ show e == "Prelude.undefined") :: SomeException -> IO Bool)
此外,这是否真的很重要,因为您会注意到它使用了几个“不安全”的功能?
朱
编辑:有些人似乎认为我声称解决了停机问题XD我不是一个坏事。我只是声明未定义的语义存在相当严重的中断,因为它们声明未定义的值应该在某种意义上与非终止无法区分。这个功能有哪些允许。我只是想检查一下人们是否同意这一点以及人们对此的看法,这是否意味着在Haskell的GHC实现中添加某些不安全功能的方便性副作用是为了方便一步到位? :)编辑:修改了编译代码
答案 0 :(得分:11)
我想制作三个ah, no,四个(相互关联的)点。
不,使用unsafe...
并不算数:
使用unsafeCoerce
显然是一个违反规则的举措,所以回答问题"这真的算了吗?":不,它不算数。 unsafe
警告所有类型的东西都会中断,包括语义:
isGood :: a -> Bool
isGood x = unsafePerformIO . fmap read $ readFile "I_feel_like_it.txt"
> isGood '4'
True
> isGood '4'
False
糟糕!根据Haskell报告,破坏了语义。哦,不,等等,我用了unsafe...
。我被警告了。
主要问题是使用unsafeCoerce
,您可以将其转换为其他内容。这与命令式编程中的类型操作一样糟糕,因此所有类型的安全性都已经消失。
您catch
IOException,而不是纯错误(⊥)。
要使用catch,您已将纯错误undefined
转换为IO异常。 IO
monad看似简单,错误处理语义不需要使用⊥。可以把它想象成monad转换器,包括某种程度上的错误处理Either
。
暂停问题的链接完全是假的
我们不需要任何编程语言的任何不安全功能来导致此区分非终止和错误。
想象一下两个程序。一个程序Char -> IO ()
输出字符,另一个程序将第一个输出写入文件,然后将该文件与字符串"*** Exception: Prelude.undefined"
进行比较,并查找其长度。我们可以使用输入undefined
或输入'c'
运行第一个。第一个是⊥,第二个是正常终止。
糟糕!我们通过区分未定义和非终止来解决停止问题。哦不,等等,不,我们实际上只区分undefined
和终止。如果我们在输入non_terminating where non_terminating = head.show.length $ [1..]
上运行这两个程序,我们发现第二个程序没有终止,因为第一个程序没有。实际上我们的第二个程序失败来解决暂停问题,因为它本身并没有终止。
停止问题的解决方案更像是总函数halts :: (a -> IO ()) -> a -> Bool
始终终止输出True
如果给定函数终止于输入a
,如果它永远不会终止,则False
。当您区分undefined
和error "user-defined error"
时,您就不可能远离这一点,这就是您的代码所做的。
因此,您对暂停问题的所有引用都令人困惑,决定一个程序是否因任何程序终止而终止。如果您使用上面的输入non-terminating
而不是undefined
,则无法得出任何结论;它在语义上已经很大程度上可以区分非终止和undefined
,并且可以将其称为解决停止问题的方法。
问题不是一个巨大的语义问题
基本上,您的所有代码都可以确定您的错误值是使用undefined
还是其他一些错误生成函数生成的。语义问题是undefined
和error "not defined with undefined"
都具有语义值⊥,但您可以区分它们。好吧,理论上说这不是很干净,但是对于different的不同原因有不同的输出是那么对调试很有用,以至于它强制执行对值的共同响应⊥ ,因为它必须始终是非终止才能完全正确。
结果是任何有bug的程序都有义务在出现错误时进入无限输出循环。这使得理论上的好处达到了深刻无益的程度。更好的方法是打印*** Exception: Prelude.undefined
或Error: ungrokable wibbles
或其他有用的描述性错误消息。
为了在危机中尽一份力,任何编程语言都必须牺牲你的愿望,让每个人都表现得彼此相同。区分不同的iss在理论上并不可爱,但在实践中不这样做是愚蠢的。
如果一个编程语言理论家称这是一个严重的语义问题,他们应该被戏弄,以便生活在一个程序冻结/不终止始终是无效输入的最佳结果的世界。
答案 1 :(得分:6)
来自the docs:
高度不安全的原语
unsafeCoerce
将任何类型的值转换为任何其他类型。毋庸置疑,如果您使用此功能,则您有责任确保旧类型和新类型具有相同的内部表示,以防止运行时损坏。
它显然也会破坏参照透明度,因此纯度,所以你放弃了Haskell语义给你的所有保证。
一旦你离开纯净的地形,有很多方法可以在脚下射击自己。您也可以使用OS原语在IO
中读取整个进程内存,以最糟糕的方式破坏引用透明度。
除此之外,您对isUndefined
的定义不解决了暂停问题,因为它不是 total ,这意味着它不会终止在所有输入上。
例如,isUndefined (last [1..])
不会终止。
有许多程序我们可以证明它们是否终止,但这并不意味着我们已经解决了暂停问题。