When using ADTs in our code base, it can be difficult to use common debugging tools like watches and breakpoints. This is mainly due to the common pattern of using compositions and other ways of manipulating how functions are called. This can cause us to have to revert to using console logs at the different points in our flow, to peek at how our data is changing over time. When using ADTs this gets even further complicated by the fact that we typically need ways to lift our logging functions into the type.
To get a handle on one way to approach debugging, we’ll look at a logAfter
function that is a must in any Functional Programmer’s toolkit. Using logAfter
we’ll hunt down a bug currently in our code base and once located, squash that bug out of existence.
Man: [00:00] Please don't be alarmed but this whole time we've had a bug in our midst. To see how this rears its ugly little head, we need to get our state in the state in which it presents. We'll simulate the initialization flow with this chain sequence and run through it step by step, starting with initialize. We'll pop downstairs and throw start in our log function to get our state instance, which we can then execute with exec with providing it a null value to see our initial state.
[00:26] Next we generate our selected cards with this start game transition, then promptly unselect all cards with this marked cards unselected, and finally round this off by randomly selecting the next hint with this next hint transaction. Notice that we get back different results with every save.
[00:42] To make this a bit easier to debug, we'll pop over to our initialize model and update our seed to use a consistent 23, getting back the same sequence with every save. We can now observe the nasty little bug by simulating a player's card selection with this select state instance.
[00:59] Seeing that the next hint is a blue square, we'll use liftA2 loaded with the crocks constant combinator, combining both start and select, expecting our is correct state to transition from null to true. But as we see here, it's reporting false. To get a handle on what the heck is going on, we jump over to our helpers to find this log after helper that takes a function and from any type A to a state S of any type B. Getting us a Kleisli that we can use in our flows.
[01:27] It takes a function FN which is used in a Kleisli composition, composing it with a function that uses the crocks tap helper for capturing the side effect of logging to our console. Lifting it into a Kleisli by using our lift state helper. We can use this helper to spy on parts of our feedback flow and remove our main log call to keep console noise to a minimum, seeing a clean console on the right.
[01:50] Over on our feedback model and inside of our feedback composition, we'll inspect this validate answer transition by wrapping it with log after, getting back false when we expected true. We'll dig a little deeper by looking at validate answer, which combines two Kleislis and compares them using the equals function. Let's see what it's comparing by wrapping them both in a log after.
[02:12] There's our problem. Card to hint returns a card with a selected true, which is not equal to a hint type. Let's dig a little deeper and take a look at card to hint, which we see is another Kleisli composition. Let's log out our get card function to see we get back a selected card, which in turn gets passed to this lifted omit function that as we see only removes the ID attribute, leaving selected on the object.
[02:37] We might be tempted to just add selected to the list and it does give us a hint, but any changes to our cards over time may require us to revisit this function over and over. A better solution would be to move to an inclusive function like we've already implemented in our turn model in this form of to hint, which instead picks only the keys that define a hint.
[02:58] To make this widely available in our project, we'll yank it out of here and instead move it into our helpers file. Back in our turn model, we now pop to the top and bring in to hint from our helpers file to keep our existing code working. We should probably test it out in our index by logging out the result of executing our start transition, as to hint is used in the next hint transaction.
[03:20] It looks like we're good to go, as blue square is our next hint. Now we can squash our bug proper style by meandering up to the top to import in our to hint helper and then search for the omit function and replace it with the new to hint helper. Let's check the results of our extermination over in our index file by using the old liftA2 over constant to provide our answer of blue square in tandem with the start state transaction, finding our bug squashed.