I am writing a bunch of tests using check-equal?
with objects that contain syntax objects that are not eq?
. For the purposes of these tests, I am okay with saying that two syntax objects are equal if they're equal when given to syntax->datum
. (Yes, I know that looses a lot of binding information, but for now, ensuring their datums are equal is good enough. I will test that the binding information is good later.)
If the two objects are not syntax objects, however, I want them to be equal if they are equal?
to each other.
However, if those objects contain syntax objects in them at some recursive point, I want to test their equality on their datums, so I can't just do:
(define (my-equal? a b)
(if (and (syntax? a) (syntax? b)
(equal? (syntax->datum a) (syntax->datum b))
(equal? a b)))
(define-binary-check (check-my-equal? my-equal? actual expected))
Because this will not do the recursive check.
I could handle the recursion myself, and only use equal?
on primitives, but that would be paramount to implementing my own equality testing.
You can use equal?/recur
to get this behavior. This function behaves like equal?
, but takes a different function in the case of a recursive call. So you could potentially implement my-equal?
like this:
(define (my-equal? actual expected)
(if (and (syntax? actual) (syntax? expected))
(equal? (syntax->datum actual) (syntax->datum expected))
(equal?/recur actual expected my-equal?)))
However, note that because you are providing the recursive function, equal?/recur
does not do any cycle detection for you. As such, you need to do it yourself. One easy way to do this is with parameters:
(define current-equal-visited (make-parameter set))
(define (my-equal? actual expected)
(cond [(set-member? (current-equal-visited) (cons actual expected))
#t]
[(and (syntax? actual) (syntax? expected))
(equal? (syntax->datum actual) (syntax->datum expected))]
[else
(parameterize ([current-equal-visited
(set-add (current-equal-visited) (cons actual expected))])
(equal?/recur actual expected my-equal?))]))
And then, of course, as you noticed in your question, you can use define-binary-check
to turn this procedure into something RackUnit can you.
(define-binary-check (check-my-equal? my-equal? actual expected))