如何处理 Haskell 中的运行时错误?

How to handle runtime errors in Haskell?

我正在尝试通过编写一个简单的控制台国际象棋游戏来学习 Haskell。它显示一个棋盘并从 SAN notation 中的标准输入获取移动。到目前为止,这是我得到的:

import System.IO
import Chess
import Chess.FEN

main = do
  putStrLn "Welcome to Console Chess!"
  putStrLn $ show defaultBoard
  gameLoop defaultBoard

gameLoop board = do
  putStr "Your move: "
  hFlush stdout
  move <- getLine
  let newBoard = moveSAN move board
  case newBoard of
   Left _ -> do
     putStrLn "Invalid move, try again..."
     gameLoop board
   Right b -> do
     putStrLn $ show b
     gameLoop b

问题是调用 moveSAN 函数有时会导致程序崩溃,丢失游戏中的所有进度。例如,在 "Your move:" 提示符下按 Enter 会产生:

Your move: 
cchess: Prelude.head: empty list

否则,输入两位数会产生:

Your move: 11
cchess: Error in array index

输入两个句点得到:

Your move: ..
cchess: Char.digitToInt: not a digit '.'

我想捕获这些错误并通知用户他们输入的移动不是有效的 SAN 表示法。我该怎么做?

在理想情况下,您会避免使用 head 这样的函数,并且根本不会出现运行时错误。运行时错误从根本上来说很尴尬,尤其是在 Haskell.

在这种情况下,您想捕获运行时错误。如果您满足于将它变成 Maybe,您可以使用 spoon 包。

Haskell 中的异常由于懒惰而变得微妙。特别是,如果您不评估结构中有异常的部分,它就不会触发。这是在 spoon 中用两个不同的函数处理的:

  • spoon,它深入评估结构,但需要一个特殊类型类
  • 的实例
  • teaspoon 只评估你的结构部分 weak head normal form

对于你的情况,我认为 teaspoon 应该没问题。尝试检查解析结果:

teaspoon (moveSAN move board)

这应该会给你一个 Maybe 值。

如果这不起作用,则意味着您需要评估更多结构以触发异常。看起来 Board 没有实现用 spoon 深入评估它所需的类型类,所以你最好的选择有点老套:在 show 的结果上使用 spoon

spoon (show $ moveSAN move board)

这会给你一个Maybe String。如果是 Just,则此着法解析正确;如果是 Nothing,则出错。

值得注意的是 spoon 包并没有真正做很多事情:它的每个功能都只有 a couple of lines。在某些时候,自己弄清楚如何处理 Haskell 中的异常是值得的,但现在 spoon 只是更方便一点,应该可以正常工作。