因此Haskell中有一个名为spoon的库,可以让我这样做
safeHead :: [a] -> Maybe a
safeHead = spoon . head
但它也让我这样做
>>> spoon True :: Maybe Bool
Just True
>>> spoon (error "fork") :: Maybe Bool
Nothing
>>> spoon undefined :: Maybe Bool
Nothing
>>> spoon (let x = x in x) :: Maybe Bool
<... let's just keep waiting...>
在某些情况下似乎非常有用,但它也违反了指称语义(据我的理解),因为它让我能够区分⊥
的语义原像中的不同事物。这比throw
/ catch
更强大,因为它们可能具有由continuation定义的语义。
>>> try $ return (error "thimble") :: IO (Either SomeException Bool)
Right *** Exception: thimble
所以我的问题是:有人可以恶意使用勺子打破类型安全吗?便利值得危险吗?或者,更现实地说,有一种合理的方式使用它可能会侵蚀某人对某项计划意义的信心吗?
答案 0 :(得分:37)
有一个棘手的问题,如果你使用它,做一个看似无辜的重构的东西可以改变一个程序的行为。没有任何花里胡哨,就是这样:
f h x = h x
isJust (spoon (f undefined)) --> True
但也许这本书中最常见的haskell转换,eta收缩,到f
,给出了
f h = h
isJust (spoon (f undefined)) --> False
由于seq
的存在,Eta收缩已经不是保留语义;但没有勺子eta收缩只能将终止程序变成错误;使用勺子eta收缩可以将终止程序更改为不同的终止程序。
正式地,spoon
不安全的方式是non-monotone on domains(因此可以根据它定义函数);而没有spoon
,每个函数都是单调的。所以技术上你失去了正式推理的有用属性。
提出一个现实生活中的例子,说明什么时候这很重要,留给读者练习(阅读:我认为现实生活中不太重要 - 除非你开始滥用它;例如使用undefined
Java程序员使用null
)
答案 1 :(得分:13)
你不能用unsafeCoerce
写spoon
,如果这是你得到的。它正如它看起来一样不合理,并且正如它所看到的那样严重违反Haskell语义。但是,您不能使用它来创建段错误或类似的东西。
然而,通过违反Haskell语义, 使代码更难以推理,并且例如,带勺子的safeHead
必然比直接写的safeHead
效率低。