Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle runtime errors in Haskell?

I'm trying to learn Haskell by writing a simple console chess game. It displays a chess board and gets moves from standard input in SAN notation. Here's what I've got so far:

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

The problem is that the call to the moveSAN function sometimes crashes the program, losing all progress made in the game. For instance, pressing Enter at the "Your move:" prompt produces:

Your move: 
cchess: Prelude.head: empty list

Otherwise, entering a two-digit number produces:

Your move: 11
cchess: Error in array index

Entering two periods gives:

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

I would like to catch these errors and inform the user that the move they have entered is not in valid SAN notation. How can I do that?

like image 529
Jaap Joris Vens Avatar asked Aug 17 '15 17:08

Jaap Joris Vens


People also ask

Does Haskell have exception handling?

The Haskell runtime system allows any IO action to throw runtime exceptions. Many common library functions throw runtime exceptions. There is no indication at the type level if something throws an exception. You should assume that, unless explicitly documented otherwise, all actions may throw an exception.

What does Just do in Haskell?

It represents "computations that could fail to return a value". Just like with the fmap example, this lets you do a whole bunch of computations without having to explicitly check for errors after each step.


1 Answers

In an ideal world, you would avoid functions like head and not have runtime errors at all. Runtime errors are fundamentally awkward, especially in Haskell.

In this case, you want to catch the runtime error. If you're content to turn it into a Maybe, you can use the spoon package.

Exceptions in Haskell are subtle thanks to laziness. Particularly, if you don't evaluate the part of the structure that has the exception, it won't fire. This is handled in spoon with two different functions:

  • spoon, which evaluates a structure deeply but requires an instance of a special typeclass
  • teaspoon which only evaluates your structure part of the way to weak head normal form

For your case, I think teaspoon should be fine. Try checking the result of the parse with:

teaspoon (moveSAN move board)

which should give you a Maybe value.

If that doesn't work, it means you need to evaluate more of the structure to hit the exception. It looks like Board does not implement the typeclass needed to deeply evaluate it with spoon, so your best bet is a bit hacky: use spoon on the result of show:

spoon (show $ moveSAN move board)

This will give you a Maybe String. If it's Just, the move parsed correctly; if it's Nothing, there was an error.

It's worth noting that the spoon package doesn't really do much: each function it has is only a couple of lines. At some point, it's worth figuring out how to handle exceptions in Haskell yourself, but for now spoon is just a bit more convenient and should work properly for you.

like image 81
Tikhon Jelvis Avatar answered Sep 28 '22 07:09

Tikhon Jelvis