Câu hỏi tương tự như câu hỏi this. Tuy nhiên, đây là một ngoại lệ, không phải về I/O lười biếng.Làm thế nào để lười biếng và ngoại lệ làm việc cùng nhau trong Haskell?
Dưới đây là một thử nghiệm:
{-# 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
Vì vậy, tôi đã viết hai chức năng fooLazy
đó là lười biếng và fooStrict
đó là nghiêm ngặt, cũng có hai bài kiểm tra testLazy
và testStrict
, sau đó tôi cố gắng nắm bắt phép chia cho không:
> test fooLazy
*** Exception: divide by zero
> test fooStrict
42
> testLazy 0
*** Exception: divide by zero
> testStrict 0
42
và không thành công trong trường hợp lười.
Việc đầu tiên mà nói đến cái tâm là để viết một phiên bản của catch
chức năng buộc các đánh giá trên số đầu tiên của nó:
{-# 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
nó dường như làm việc:
> test fooLazy
42
> test fooStrict
42
> testLazy 0
42
> testStrict 0
42
nhưng tôi sử dụng hàm unsafePerformIO
ở đây và điều này thật đáng sợ.
Tôi có hai câu hỏi:
- người ta có thể chắc chắn rằng
catch
chức năng luôn bắt tất cả các trường hợp ngoại lệ, không phụ thuộc vào bản chất của nó đối số đầu tiên? - Nếu không, có cách nào nổi tiếng để giải quyết vấn đề này không? Một cái gì đó giống như chức năng
catchStrict
là phù hợp?
UPDATE 1.
Đây là một phiên bản tốt hơn của catchStrict
chức năng bởi 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`)
UPDATE 2.
Dưới đây là một 'xấu' dụ:
main :: IO()
main = do
args <- getArgs
res <- return ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> return 0
print res
Nó nên được viết lại như thế này:
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 $!)
CẬP NHẬT 3.
Vì đã nhận thấy nanothief, không đảm bảo rằng chức năng catch
luôn bắt bất kỳ ngoại lệ nào. Vì vậy, một trong những cần phải sử dụng nó một cách cẩn thận.
Vài lời khuyên về cách giải quyết vấn đề có liên quan:
- Sử dụng
($!)
vớireturn
, sử dụngforceM
trên số đầu tiên củacatch
, sử dụngcatchStrict
chức năng. - Tôi cũng nhận thấy rằng đôi khi mọi người add some strictness cho các trường hợp máy biến áp của họ.
Dưới đây là một ví dụ:
{-# 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
Nó thực chất là the identity monad transformer, nhưng với nghiêm ngặt 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
Vì vậy, quy tắc là tôi cần phải vượt qua hành động IO thực (chẳng hạn như 'in') đối với hàm' catch', không phải 'trả về' cho một số giá trị thuần túy? – JJJ
@ht .: Tôi đã thêm nhiều câu trả lời của mình để giải thích khi giá trị được đánh giá. –
OK, cả hai ví dụ đều hoạt động tốt, 'fooEvaluated' cần đánh giá phân chia cho khớp mẫu, do đó ngoại lệ được ném và xử lý với hành động tùy chỉnh (' return 42' trong ví dụ của tôi), 'fooNoException' không cần phân chia tất cả và chỉ trả về '1', nó giống như' fooNoException m = const (return 1) (trả về (3 div m) :: IO Int) '. Khi tôi hỏi về quy tắc, tôi có nghĩa là cách duy nhất để bỏ lỡ một ngoại lệ (để hành động tùy chỉnh không được thực hiện) là 'catch' trên' return' cho 'm div 0',' head [] ',' fromJust không có gì, bất kỳ chức năng một phần, vv Nó thực sự là cách duy nhất? – JJJ