Saturday, April 18, 2009

Arc errors (slightly) demystified

If you've programmed in Arc, you've probably noticed that the error messages are often cryptic at best. Error handling is a major weakness of the current Arc implementation; I've wasted a lot of time trying to figure out why my code fails, because the error message has no connection to the actual problem.

This article gives examples of some mysterious error messages, how they arise, and what they mean. Hopefully this can save some debugging time.

Usually Arc doesn't provide any indication of where the error occurred. In the cases where Arc does provide location information, the location is a byte offset, not a line number, which is hard to interpret:
arc> (load "t.arc")
Error: "t.arc::720: read: expected a ')'"
If you're loading a file, how do you find byte offset 720? One way is dd if=t.arc bs=1 skip=720 which will display the file from the 720th character onward.

Similar errors can be generated from REPL input. In this case, the unexpected parenthesis is at character 910 that's been typed to the REPL (although that's unlikely to be useful):

arc> )
Error: "UNKNOWN::910: read: unexpected ')'"

arc> (def foo (x) (prn x))
#<procedure: foo>
arc> (foo 1 2)
Error: "procedure  foo: expects 1 argument, given 2: 1 2"
This error message is straightforward - too many arguments were given to the procedure..
arc> (def (bar (x) (prn x)))
Error: "#<procedure>: expects at least 2 arguments, given 1: (bar (x . nil) (prn x . nil) . nil)"
In this case, the expected arguments error is more mysterious. Misplaced parentheses generate this error from the depths of the interpreter.
arc> (+ 1 ("s"))
Error: "car: expects argument of type <pair>; given ()"
You probably have extra parentheses, attempting to index into a string. For a number, the error message is much clearer:
arc> (+ 1 (42))
Error: "Function call on inappropriate object 42 ()"

arc> (def foo (a (b c)) (prn a))
arc> (foo 1 2)
Error: "car: expects argument of type <pair>; given 2"
Destructuring bind fails because argument format doesn't match function definition.
arc> (def f (o x (table)) (prn x))
arc> (f 42)
Error: "car: expects argument of type <pair>; given ()"
You need two sets of parentheses in the def, one around the arguments, and one around the optional argument:
arc> (def f ((o x (table))) (prn x))

arc> (+ 1 "s")
Error: "+: expects type  as 2nd argument, given: \"s\"; other arguments were: 1"
Straightforward: wrong type to +.
(= t 42)
Error: "Can't rebind t"
The atoms t and nil can't be changed. Don't use t as a variable.
arc> (map (prn) '(a b c))
Error: "Function call on inappropriate object nil (a)"
You need to pass a function to map, such as prn or [prn _] rather than (prn)
arc> (car "abc")
Error: "Can't take car of \"abc\""
Car and cdr don't work on strings. You'll also get this error if the function erroneously uses car on a string internally:
arc> (counts "abc")
Error: "Can't take car of \"abc\""

arc> (with a 4 (+ a 1))
Error: "Can't take cdr of a"
The mysterious "can't take cdr" error shows up if you don't have parentheses around the variable assignments. You may be thinking of let, which doesn't use the parentheses and assigns a single variable.

Let me know if you have any other candidates for mystery errors.