Die Frage ist ähnlich this Frage. In diesem Fall geht es jedoch um Ausnahmen, nicht um faule E/A. HierWie funktionieren Faulheit und Ausnahmen in Haskell zusammen?
ist ein Test:
{-# 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
Also schrieb ich zwei Funktionen fooLazy
, die faul und fooStrict
ist, die streng ist, auch gibt es zwei Tests testLazy
und testStrict
, dann versuche ich Division durch Null zu fangen:
> test fooLazy
*** Exception: divide by zero
> test fooStrict
42
> testLazy 0
*** Exception: divide by zero
> testStrict 0
42
und es scheitert in faulen Fällen.
Das erste, was ist in den Sinn kommt eine Version der catch
Funktion zu schreiben, die die Bewertung auf das erste Argument erzwingen:
{-# 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
es scheint zu funktionieren:
> test fooLazy
42
> test fooStrict
42
> testLazy 0
42
> testStrict 0
42
aber ich Verwenden Sie die unsafePerformIO
Funktion hier und das ist gruselig.
Ich habe zwei Fragen:
- kann man sicher sein, dass die
catch
Funktion immer alle Ausnahmen abfängt, und zwar unabhängig von der Art des es erstes Argumente? - Wenn nicht, gibt es einen bekannten Weg, um mit dieser Art von Problemen umzugehen? So etwas wie die
catchStrict
Funktion ist geeignet?
UPDATE 1.
Dies ist eine bessere Version der catchStrict
Funktion von 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. Hier
ist eine andere 'schlechte' Beispiel:
main :: IO()
main = do
args <- getArgs
res <- return ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> return 0
print res
Es sollte wie folgt neu geschrieben werden:
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 $!)
UPDATE 3.
Wie nanothief bemerkt, gibt es keine Garantie, dass die catch
Funktion immer irgendeine Ausnahme abfangen. Also muss man es vorsichtig benutzen.
Ein paar Tipps, wie die damit verbundenen Probleme zu lösen:
- Verwenden
($!)
mitreturn
verwendenforceM
auf das erste Argument voncatch
, verwenden Sie diecatchStrict
Funktion. - Ich bemerkte auch, dass manchmal Menschen add some strictness zu Instanzen ihrer Transformatoren.
Hier ein Beispiel:
{-# 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
Es ist im Wesentlichen the identity monad transformer, aber mit strengen 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
Also ist die Regel, dass ich echte IO-Aktion (wie "print") an die "catch" -Funktion übergeben muss, nicht die "Rückkehr" für einige reine Wert? – JJJ
@ht .: Ich habe meiner Antwort mehr hinzugefügt, um zu erklären, wann die Werte ausgewertet werden. –
OK, beide Beispiele funktionieren gut, 'fooEvaluated' muss Division für Mustervergleich auswerten, so dass die Ausnahme ausgelöst und mit einer benutzerdefinierten Aktion behandelt wird (' return42' in meinem Beispiel), 'fooNoException' braucht keine Division bei alles und gibt nur '1 'zurück, es ist dasselbe wie' fooNoException m = const (Rückkehr 1) (Rückkehr (3 div m) :: IO Int) '. Wenn ich nach der Regel frage, meine ich, dass die einzige Möglichkeit, eine Ausnahme zu verpassen (so dass eine benutzerdefinierte Aktion nicht ausgeführt wird), '' '' return 'für 'm div 0',' head [] ',' fromJust nichts, irgendeine Teilfunktion, etc. Es ist wirklich der einzige Weg? – JJJ