For many students taking CSC 430, programming in a functional style is uncomfortable and challenging. Tools that worked well before don’t seem to be available, and the landscape seems strange.
One of the most significant issues surrounds the process of debugging programs.
In imperative languages such as C, Assembly, Java, and Python, it’s common to write complex functions involving internal control flow, particularly loops, combined with variables that are being updated as the function runs. In order to figure out what’s going on when a problem occurs in such a program, it’s pretty important to be able to use a debugger; a debugger is the best way to figure out what sequence of updates led to the problem.
In functional languages such as Racket, there tends to be much less mutation, and also less internal control flow that doesn’t involve function calls. This means that nearly all problems can be debugged by formulating smaller test cases, and breaking down large problems into smaller ones.
This means that debuggers are hardly used at all in the functional programming community, they’re just not that useful.
However, when first getting started with functional programming, the lack of debuggers can make it hard to "explore" how the language works, especially with incomplete knowledge of the language forms.
In these cases, it can be very useful to do some simple "print debugging"; inserting code that prints the values of certain variables. Since Racket is not purely functional, you can definitely do this.
Specifically, suppose that somewhere in your code you have a call to a function f with arguments x and y:
... (f x y) ...
If I want to see what value x has, I can print it, like this:
... (begin (printf "x is currently ~v\n" x) (f x y)) ...
That is: directly replace the (f x y) with the begin block. In fact, you could even put it tighter in, right around the x itself:
... (f (begin (printf "x is currently ~v\n" x) x) y) ...
... if you don’t find that too confusing.
If you’re still with me, and you find that useful but too time-consuming, you can highlight an expression and use (control-c control-l) to wrap it. Or not.
So, the takeaway here is that printf debugging is totally available to you, in situations where it’s helpful to you.
The other takeaway here is that generally speaking, minimizing a failing test case gives you more bang for your buck. And also, thinking about what each piece of the function will return, for your specific test case.