为什么使用Maybe时catch不能正确调用处理程序?

1 haskell exception lazy-evaluation catch-block

考虑以下 Haskell 代码

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))
Run Code Online (Sandbox Code Playgroud)

我打算让这段代码工作的方式是评估表达式lex_rgx rgx_str,在它们发生时捕获任何异常,然后调用disp_lex_error以漂亮地打印错误。

(顺便说一下,disp_lex_error的代码如下

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
Run Code Online (Sandbox Code Playgroud)

)

但是,我认为懒惰的评估正在阻止这种情况发生。当我在 ghci 中使用调用错误的输入运行代码时,我收到以下消息

*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" ""
Run Code Online (Sandbox Code Playgroud)

而不是一个漂亮的印刷错误。

对我来说,似乎代码没有按照我的意图工作,因为由于延迟评估,系统仅在表达式evaluate $ lex_rgx rgx_str 已经开始形成父表达式后才实际计算表达式Just x

我是否正确,如果正确,是否有任何优雅的方法来规避这个问题?

Li-*_*Xia 5

evaluate仅将值计算为 WHNF(即匹配(:)or [],但不能进一步匹配),但您的错误隐藏在列表元素内。以下函数强制列表的所有元素(或使用 deepseq 库以获得更通用的接口)。

-- 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
Run Code Online (Sandbox Code Playgroud)

然而,在元素隐藏例外情况是有问题的方法,首先,考虑重构词法分析器不使用异常,产生一个信息Either是继电器的所有可能的错误,而不是使用MaybeJust甚至不保证一次成功的解析。