Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to completely abort a Haskell runtime?

Tags:

haskell

I have a function

import System.Exit

exit_and_report_type_mismatch :: String -> IO ExitCode
exit_and_report_type_mismatch error_message = do
    putStrLn error_message
    exitFailure

and a section of another like so

interpret_expr :: Vars -> Expr -> Val        
interpret_expr vars (Plus (ConsE _ _) (NumE _)) = exit_and_report_type_mismatch "Type Error: Can only concatenate list (not int) to list"

Haskell complains to me that it is expecting type Val (another data type I have defined) but it actually receives type IO Exitcode. Fair enough - exit_and_report_mismatch is returning IO ExitCode which is not a Val.

How do I completely abort the Haskell program from within "exit_and_report_type_mismatch"? I have read a bit about Haskell exceptions but the explanations either do not make sense or mention having to call ExitWith from the main function, which is not an option.

like image 990
nebffa Avatar asked Dec 26 '22 06:12

nebffa


1 Answers

This is what error is for. From the documentation:

error :: [Char] -> a
error stops execution and displays an error message.

For instance:

zsh% runhaskell <<<'main = putStrLn (error "Message") >> print "Not reached."'
runghcXXXX7729.hs: Message

The effect of putStrLn is ignored, and the program terminates as soon as the value produced by error is demanded (lazy evaluation means that just putting error somewhere doesn't immediately cause an error; as you might or might not expect, let x = error "Message" in putStrLn "Printed" causes no errors). It is possible to catch these exceptions with the functions from Control.Exception.Base, such as catch, but I've never done this nor have I seen this done.

Also, as a final note, consider avoiding the use of error. Partial functions (functions that aren't defined over their entire input domain) are best avoided when possible, as it's much easier to reason about your code with the stronger guarantees total functions provide. It's nice when, as for total functions, f :: A -> B really means "the function f returns something of type B"; for partial functions, f :: A -> B means only "if the function f returns, then what it returns is of type B". In your case, this might mean having a type like interpretExpr :: Vars -> Expr -> Either RuntimeError Val, or something suitably isomorphic (in the simplest case, perhaps data Result = Error String | Value Val, and interpretExpr :: Vars -> Expr -> Result).

like image 125
Antal Spector-Zabusky Avatar answered Jan 13 '23 21:01

Antal Spector-Zabusky