1

Consider the following Haskell code

try_lex_rgx :: String -> IO (Maybe [RgxToken])

try_lex_rgx rgx_str =
  catch
    (do
        rgx_toks <- evaluate $ lex_rgx rgx_str
        return $ Just rgx_toks)
    (\(LexerErr err_msg remainder) -> disp_lex_error rgx_str (LexerErr err_msg remainder))

The way I intend for this code to work is to evaluate the expression lex_rgx rgx_str, catching any exceptions when they occur, and then invoking disp_lex_error to pretty-print the error.

(By the way, the code for disp_lex_error is the following

disp_lex_error :: String -> RgxLexerException -> IO (Maybe [RgxToken])

disp_lex_error rgx_str (LexerErr err_msg remainder) = let loc_str_frag = "In regex " ++ rgx_str ++ " at ..." in
                                                        do 
                                                            hPutStrLn stderr ("Lexer error: " ++ err_msg ++ "\n" ++ 
                                                                              loc_str_frag ++ remainder ++ "\n" ++
                                                                              (replicate (length loc_str_frag - 1) ' ') ++ "^^")
                                                            evaluate Nothing

)

However, I think lazy evaluation is stopping this from happening. When I run the code in ghci with an input that invokes an error, I get the following message

*Rgx.RgxLexer> :l Rgx.RgxLexer
[1 of 2] Compiling Rgx.RgxTok       ( Rgx/RgxTok.hs, interpreted )
[2 of 2] Compiling Rgx.RgxLexer     ( Rgx/RgxLexer.hs, interpreted )
Ok, two modules loaded.
*Rgx.RgxLexer> try_lex_rgx "\\"
Just [*** Exception: LexerErr "Dangling \"\\\" is not allowed" ""

instead of a pretty printed error.

To me, it seems as though the code is not working as I intend it to because due to lazy evaluation, the system only actually calculates the expression evaluate $ lex_rgx rgx_str once it has already started forming the parent expression Just x.

Am I correct, and if so, is there any elegant way to circumvent this issue?

Mark Mizzi
  • 11
  • 1
  • In haskell IO is strict, so `evaluate`'s effects are evaluated strictly. However, it's return value might still be lazy. This is not a problem if you throw your parse errors with `throwIO` or `throwM`, but it is a problem if you use just `throw`. Which one do you use? – MorJ Jul 27 '21 at 15:29

1 Answers1

4

evaluate only evaluates a value to WHNF (i.e., to match (:) or [], but not further), but your error is hidden inside an element of the list. The following function forces all elements of a list (or use the deepseq library for a more general interface).

-- Evaluates each element to WHNF (which might not be enough for all use cases)
evaluateList :: [a] -> IO [a]
evaluateList xs = evaluate (foldr seq () xs) >> pure xs

However hiding exceptions in elements is a questionable methodology to begin with, consider refactoring the lexer to not use exceptions, producing an informative Either that relays all possible errors, instead of using Maybe whose Just doesn't even guarantee a successful parse.

Li-yao Xia
  • 31,896
  • 2
  • 33
  • 56
  • I did try using deepseq's force method but the issue persisted. In any case, I think I will refactor the code to not use exceptions. Thank you for the suggestion. – Mark Mizzi Jul 27 '21 at 16:43