在Haskell中,您可以从纯函数代码中抛出异常,但是您只能捕获IO代码。
答案 0 :(得分:14)
评论中提到的paper delnan以及this previous question的答案肯定提供了仅在IO
中捕获例外的充分理由。
但是,我也可以看到为什么观察评价顺序或打破单调性等原因在直观层面上可能没有说服力;很难想象如何在绝大多数代码中造成很大的伤害。因此,可能有助于回忆一下,异常处理是一种明显非本地变种的控制流结构,并且能够捕获纯代码中的异常将允许(错误地)将它们用于此目的。
请允许我准确说明这会带来什么样的恐怖。
首先,我们定义要使用的异常类型,以及可以在纯代码中使用的catch
版本:
newtype Exit a = Exit { getExit :: a } deriving (Typeable)
instance Show (Exit a) where show _ = "EXIT"
instance (Typeable a) => Exception (Exit a)
unsafeCatch :: (Exception e) => a -> (e -> a) -> a
unsafeCatch x f = unsafePerformIO $ catch (seq x $ return x) (return . f)
这将让我们抛出任何Typeable
值,然后从一些外部范围捕获它,而不需要任何干预表达式的同意。例如,我们可以在我们传递给高阶函数的内容中隐藏Exit
抛出,以通过其评估生成的某个中间值来逃避。精明的读者可能已经想到了现在的发展方向:
callCC :: (Typeable a) => ((a -> b) -> a) -> a
callCC f = unsafeCatch (f (throw . Exit)) (\(Exit e) -> e)
是的,这确实有效,但需要注意的是,只要整个表达式,它就需要使用延续来进行评估。如果您尝试这一点,请记住这一点,或者只使用deepseq
,如果来自轨道的nuking更多是你的速度。
看哪:
-- This will clearly never terminate, no matter what k is
foo k = fix (\f x -> if x > 100 then f (k x) else f (x + 1)) 0
可是:
∀x. x ⊢ callCC foo
101
从map
:
seqs :: [a] -> [a]
seqs xs = foldr (\h t -> h `seq` t `seq` (h:t)) [] xs
bar n k = map (\x -> if x > 10 then k [x] else x) [0..n]
请注意强制评估的必要性。
∀x. x ⊢ callCC (seqs . bar 9)
[0,1,2,3,4,5,6,7,8,9]
∀x. x ⊢ callCC (seqs . bar 11)
[11]
...啊。
现在,让我们再也不谈这个了。