2017-07-15 1 views
2

In einem Monad-Transformer-Stack ExceptT werden Bindungsausdrücke nach throwE nicht ausgeführt.Ausgenommener Deadcode-Erkennung

  1. Stimmt das?
  2. Wenn ja, wie kann ich den Compiler oder meine Werkzeuge so konfigurieren, dass toter Code wie in diesem Beispiel erkannt wird?
{-# LANGUAGE 
    GeneralizedNewtypeDeriving 
#-} 

import Control.Monad.Except (MonadError (..), MonadIO, liftIO) 
import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE) 

newtype MyTrans e a = MyTrans { unMyTrans :: ExceptT e IO a } 
    deriving (Functor, Applicative, Monad, MonadIO, MonadError e) 

throwMT :: e -> MyTrans e a 
throwMT = MyTrans . throwE 

runMyTrans :: MyTrans e a -> IO (Either e a) 
runMyTrans = runExceptT . unMyTrans 

comp = do 
    liftIO $ putStrLn "Starting Computation" 
    throwMT "Some Error" 
    -- dead code from this line 
    liftIO $ putStrLn "Ending Computation" 
    return() 


main = print =<< runMyTrans comp 
+0

Sie werden definitiv ausgeführt, es ist nur so, dass sie als No-Op funktionieren. Wenn Sie diese Funktionalität nicht wünschen, verwenden Sie diese Funktion nicht. – 4castle

+0

Ich verstehe es nicht, aus meiner Sicht wird nichts nach 'throwE' ausgewertet, zum Beispiel führt diese' throwMT "Some Error" >> = undefined' nicht zu einem Laufzeitfehler. – homam

+1

Die Bindung wird ausgewertet, aber der Funktionsparameter wird ignoriert, wenn eine Ausnahme aufgetreten ist. Das ist Teil der Definition von "AusgenommenT". Man könnte es toten Code nennen, aber dieses Verhalten ist ein Punkt der Monade. – 4castle

Antwort

3
  1. Es ist wahr.
  2. Ich kenne keine Tools zur Entfernung von toten Codes, die heute für Haskell verfügbar sind.
2

Lassen Sie uns die MyTrans newtype Lärm ignorieren und nur Blick auf

throwE x >>= f :: ExceptT e IO a 

in Control.Monad.Trans.Except Graben, wir

newtype ExceptT e m a = 
    ExceptT {runExceptT :: m (Either e a)} 

throwE :: Monad m => e -> ExceptT e m a 
throwE e = ExceptT (pure (Left e)) 

instance Monad m => Monad (ExceptT e m) where 
    m >>= f = ExceptT $ do 
    res <- runExceptT m 
    case res of 
     Left e -> pure (Left e) 
     Right r -> runExceptT (f r) 

Inlining diese (etwas GHC tun mag) zu sehen, die wir sehen, dass throwE e >>= f ist

ExceptT $ do 
    res <- pure (Left e) 
    case res of 
    Left e -> pure (Left e) 
    Right r -> runExceptT (f r) 

Nur für eine beliebige Monade vorausgesetzt, würde GHC nicht weiter kommen, weil es nicht weiß, dass m eine gesetzestreue Monade ist und nicht nach "freien Theoremen" sucht, die möglicherweise helfen könnten.

Dies ist jedoch keine willkürliche Monade! Es ist IO!

newtype IO a = 
    IO (State# RealWorld 
      -> (# State# RealWorld, a #)) 
unIO :: IO a -> State# RealWorld 
      -> (# State# RealWorld, a #) 
unIO (IO f) = f 

instance Monad IO where 
    return a = IO $ \s -> (# s, a #) 
    IO m >>= f = IO $ \s -> 
    case m s of 
     (# s', a #) -> unIO (f a) s' 

Die linke Identität Gesetz „bewiesen“ für IO durch Optimierung werden kann.

return a >>= g 
= inlining 
IO $ \s -> 
    case (\t -> (# t, a #)) s of 
    (# s', r #) -> unIO (g r) s' 
= beta reduction 
IO $ \s -> 
    case (# s, a #) of 
    (# s', r #) -> unIO (g r) s' 
= case of known constructor 
IO $ \s -> unIO (g a) s 
= (assuming g has arity at least two, which it will here) 
g a 

diese Informationen anwenden, throwE e >>= f wird

ExceptT $ 
    (\res -> case res of 
    Left e -> pure (Left e) 
    Right r -> runExceptT (f r)) (Left e) 

Beta zu reduzieren und die Anwendung bei bekannten Konstruktor Ausbeuten

ExceptT $ pure (Left e) 

Der tote Code vollständig beseitigt ist.