gpt4 book ai didi

exception - 懒惰和异常如何在 Haskell 中协同工作?

转载 作者:行者123 更新时间:2023-12-04 03:59:23 24 4
gpt4 key购买 nike

问题类似于 this问题。然而,这个是关于异常的,而不是关于惰性 I/O。

这是一个测试:

{-# LANGUAGE ScopedTypeVariables #-}

import Prelude hiding ( catch )
import Control.Exception

fooLazy :: Int -> IO Int
fooLazy m = return $ 1 `div` m

fooStrict :: Int -> IO Int
fooStrict m = return $! 1 `div` m

test :: (Int -> IO Int) -> IO ()
test f = print =<< f 0 `catch` \(_ :: SomeException) -> return 42

testLazy :: Int -> IO Int
testLazy m = (return $ 1 `div` m) `catch` \(_ :: SomeException) -> return 42

testStrict :: Int -> IO Int
testStrict m = (return $! 1 `div` m) `catch` \(_ :: SomeException) -> return 42

所以我写了两个函数 fooLazy这是懒惰和 fooStrict这是严格的,还有两个测试 testLazytestStrict ,然后我尝试将除以零:
> test fooLazy
*** Exception: divide by zero
> test fooStrict
42
> testLazy 0
*** Exception: divide by zero
> testStrict 0
42

在懒惰的情况下它会失败。

首先想到的是写一个版本的 catch强制对其第一个参数进行评估的函数:
{-# LANGUAGE ScopedTypeVariables #-}

import Prelude hiding ( catch )
import Control.DeepSeq
import Control.Exception
import System.IO.Unsafe

fooLazy :: Int -> IO Int
fooLazy m = return $ 1 `div` m

fooStrict :: Int -> IO Int
fooStrict m = return $! 1 `div` m

instance NFData a => NFData (IO a) where
rnf = rnf . unsafePerformIO

catchStrict :: (Exception e, NFData a) => IO a -> (e -> IO a) -> IO a
catchStrict = catch . force

test :: (Int -> IO Int) -> IO ()
test f = print =<< f 0 `catchStrict` \(_ :: SomeException) -> return 42

testLazy :: Int -> IO Int
testLazy m = (return $ 1 `div` m) `catchStrict` \(_ :: SomeException) -> return 42

testStrict :: Int -> IO Int
testStrict m = (return $! 1 `div` m) `catchStrict` \(_ :: SomeException) -> return 42

它似乎工作:
> test fooLazy
42
> test fooStrict
42
> testLazy 0
42
> testStrict 0
42

但我使用 unsafePerformIO在这里起作用,这很可怕。

我有两个问题:
  • 可以确定catch函数总是捕获所有异常,而不管它的第一个参数的性质如何?
  • 如果没有,是否有一种众所周知的方法来处理这类问题?类似于 catchStrict功能合适吗?


  • 更新 1 .

    这是 catchStrict 的更好版本功能由 nanothief :
    forceM :: (Monad m, NFData a) => m a -> m a
    forceM m = m >>= (return $!) . force

    catchStrict :: (Exception e, NFData a) => IO a -> (e -> IO a) -> IO a
    catchStrict expr = (forceM expr `catch`)

    更新 2 .

    这是另一个“坏”的例子:
    main :: IO ()
    main = do
    args <- getArgs
    res <- return ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> return 0
    print res

    应该这样重写:
    main :: IO ()
    main = do
    args <- getArgs
    print ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> print 0
    -- or
    --
    -- res <- return ((+ 1) $ read $ head args) `catchStrict` \(_ :: SomeException) -> return 0
    -- print res
    --
    -- or
    --
    -- res <- returnStrcit ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> return 0
    -- print res
    --
    -- where
    returnStrict :: Monad m => a -> m a
    returnStrict = (return $!)

    更新 3 .

    nanothief注意,不能保证 catch函数总是捕获任何异常。所以需要谨慎使用。

    关于如何解决相关问题的一些提示:
  • 使用($!)return , 使用 forceMcatch 的第一个参数上, 使用 catchStrict功能。
  • 我还注意到有时人们 add some strictness到他们的变压器的实例。

  • 这是一个例子:
    {-# LANGUAGE GeneralizedNewtypeDeriving, TypeSynonymInstances, FlexibleInstances
    , MultiParamTypeClasses, UndecidableInstances, ScopedTypeVariables #-}

    import System.Environment

    import Prelude hiding ( IO )
    import qualified Prelude as P ( IO )
    import qualified Control.Exception as E
    import Data.Foldable
    import Data.Traversable
    import Control.Applicative
    import Control.Monad.Trans
    import Control.Monad.Error

    newtype StrictT m a = StrictT { runStrictT :: m a } deriving
    ( Foldable, Traversable, Functor, Applicative, Alternative, MonadPlus, MonadFix
    , MonadIO
    )

    instance Monad m => Monad (StrictT m) where
    return = StrictT . (return $!)
    m >>= k = StrictT $ runStrictT m >>= runStrictT . k
    fail = StrictT . fail

    instance MonadTrans StrictT where
    lift = StrictT

    type IO = StrictT P.IO

    instance E.Exception e => MonadError e IO where
    throwError = StrictT . E.throwIO
    catchError m h = StrictT $ runStrictT m `E.catch` (runStrictT . h)

    io :: StrictT P.IO a -> P.IO a
    io = runStrictT

    本质上是 the identity monad transformer , 但严格 return :
    foo :: Int -> IO Int
    foo m = return $ 1 `div` m

    fooReadLn :: Int -> IO Int
    fooReadLn x = liftM (`div` x) $ liftIO readLn

    test :: (Int -> IO Int) -> P.IO ()
    test f = io $ liftIO . print =<< f 0 `catchError` \(_ :: E.SomeException) -> return 42

    main :: P.IO ()
    main = io $ do
    args <- liftIO getArgs
    res <- return ((+ 1) $ read $ head args) `catchError` \(_ :: E.SomeException) -> return 0
    liftIO $ print res

    -- > test foo
    -- 42
    -- > test fooReadLn
    -- 1
    -- 42
    -- ./main
    -- 0

    最佳答案

    首先(我不确定您是否已经知道这一点),catch 不适用于惰性案例的原因是

    1 `div` 0

    表达式只有在需要时才会计算,它位于 print 中。功能。但是, catch方法仅适用于 f 0表达,而不是全部 print =<< f 0表达式,因此不会捕获异常。如果你这样做了:
    test f = (print =<< f 0) `catch` \(_ :: SomeException) -> print 42

    相反,它在这两种情况下都能正常工作。

    如果你想创建一个强制对 IO 结果进行完整评估的 catch 语句,而不是创建一个新的 NFData 实例,你可以写一个 forceM方法,并在 catchStrict 中使用方法:
    forceM :: (Monad m, NFData a) => m a -> m a
    forceM m = m >>= (return $!) . force

    catchStrict :: (Exception e, NFData a) => IO a -> (e -> IO a) -> IO a
    catchStrict expr = (forceM expr `catch`)

    (我有点惊讶 forceM 不在 Control.DeepSeq 库中)

    关于您的评论:

    不,规则是仅在计算值时抛出异常,并且仅在 haskell 需要时才进行。如果 haskell 可以延迟对某事的评估,它会。

    不使用 $! 的示例测试函数,但仍会立即导致异常(因此正常的 catch 将捕获除以零异常)是:
    fooEvaluated :: Int -> IO Int
    fooEvaluated m = case 3 `div` m of
    3 -> return 3
    0 -> return 0
    _ -> return 1

    Haskell 被迫评估“3 `div` m”表达式,因为它需要将结果与 3 和 0 进行匹配。

    作为最后一个示例,以下内容不会引发任何异常,并且在与测试函数一起使用时返回 1:
    fooNoException :: Int -> IO Int
    fooNoException m = case 3 `div` m of
    _ -> return 1

    这是因为 haskell 永远不需要计算 "3 `div` m"表达式(因为 _ 匹配所有内容),因此永远不会计算,因此不会抛出异常。

    关于exception - 懒惰和异常如何在 Haskell 中协同工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11149876/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com