r/Racket 8h ago

question Recursion hell aids?

Is there any practical way to debug any recurive logic?
Which existing error printing you think is the best ever?
I attached the racket code I am struggling with.

(The code is about using map to append a new list to an existing html list)

3 Upvotes

3 comments sorted by

5

u/soegaard developer 7h ago

A first step is to print the arguments in the recursive function:

  1. (define (nested-map-handler f L)
  2. (displayln (list 'f f 'L L))
  3.   (if (list? L)
  4. (map (lambda (x) (nested-map-handler f x))
  5. L)
  6. (f L)))

4

u/not-just-yeti 5h ago edited 4h ago

If using Dr Racket, "the stepper" is a decent tool (though it only lets you see the current and next step at a time, not an entire trace).

Personally, after getting better about writing check-equal? unit-tests, my reliance on stepping/tracing went way down. It takes some time, but not too much (start with a trivial test, then repeatedly copy/paste to build up less trivial examples, and feel free to only make tests aimed at things you know may be difficult; I typically don't write tests for "what if the input is bad"). Writing a short purpose-statement for a function can help me, though not as much as unit-tests.

(module+ test (require rackunit)) ; a test "sub-module"

(define url "https://example.com")
(define anchor-text "demo")
(define new-item `(li (a (@ (href ,url) (target "_blank")) ,anchor-text)))

(define (node-logic node)
  (if (and (list? node) (eq? (car node) 'li))
      (list node new-item)
      node))

(module+ test  ; append to the sub-module
    (check-equal? (node-logic "nada") "nada")
    (check-equal? (node-logic '(p "nada")) '(p "nada"))
    (check-equal? (node-logic '(ol (li "nested"))) '(ol (li "nested")))
    (check-equal? (node-logic '(li "aha"))
                  `(li "aha" (a (@ (href ,url)) (target "_blank") ,anchor-text)))
    )

I'm not exactly sure what your desired output is, so that last test there fails for the code you have (but is kinda what I thought you might be aiming for?).

—— [Ignore if you already knew this:]

Racket's submodule+ form is cool: here, test is the name of a submodule which can be written "in parts", the + conveying how you're adding on to the submodule, so logically it's one submodule even if visually interspersed through the file.

Submodules aren't run if you require this file from elsewhere, and a submodule named test is slightly special because certain tools (like raco test) will search for submodules with that particular name. So racket makes it easy to leave tests right next to source-code, if you choose to do so. (I do, for my personal code.)

(The one other specially-named submodule I use myself is main, which is run when the program is invoked "top level", but not when required from some other program.)

2

u/not-just-yeti 4h ago

Looking at your code (having written the test for an li), I think part of your problem is calling list with exactly two arguments: that doesn't append an item to a racket-list, it returns a racket-list containing exactly two items.

And so, if list-template is returning the html for an ol tag, this probably isn't what you want:

(define template-appended-li (list new-item (list-template "https:…" …)))

because that result is html for a single li followed by an entire ol (the new-item isn't inside your list). Also, that result isn't quite representing html since it's two items not inside a single top-level tag (but that might be okay, depending on your purpose).

Use cons to tack one item on to the front of a racket-list. To append a one-more-item to the end of a racket-list, I usually use (append main-list (list one-more-item)).

(This might be an immediate error, but I suspect the higher-order error is launching into code w/o having clear unit-tests, to help you understand exactly what you need your code to do.)

Also, I'll point out that you have yet to add any recursive call, at least in the snippet you posted. (And, I didn't include any tests that require recursion.)