I don’t understand what the differences are between (sorry for the contrived example):
(define average
(lambda (elems)
(define length
(lambda (xs)
(if (null? xs)
0
(+ 1 (length (cdr xs))))))
(define sum
(lambda (xs)
(if (null? xs)
0
(+ (car xs) (sum (cdr xs))))))
(define total (sum elems))
(define count (length elems))
(/ total count)))
and
(define average
(lambda (elems)
(letrec ((length
(lambda (xs)
(if (null? xs)
0
(+ 1 (length (cdr xs))))))
(sum
(lambda (xs)
(if (null? xs)
0
(+ (car xs) (sum (cdr xs))))))
(total (sum elems))
(count (length elems)))
(/ total count))))
As far as I can tell, they both create a new scope, and in that scope create 4 local variables that refer to each other and to themselves, and evaluate and return a body.
Am I missing something here, or is letrec synonymous with scoped defines?
I know this may be implementation dependent; I’m trying to get an understanding of the fundamentals of Lisps.
You are correct that there are parallels between the
defineandletrecversions of your code. However, the devil is in the details. In R5RS, internaldefinehasletrecsemantics. In R6RS, internaldefinehasletrec*semantics.What’s the difference? Your code has actually just highlighted this difference. As Zhehao’s answer mentions, your definition of
totalandcountinside the sameletrecas thelengthandsumis incorrect:lengthandsumare not guaranteed to be bound by the time you’re evaluating the values of(length elems)and(sum elems), since the binding of those variables is not guaranteed to be left-to-right.letrec*is similar toletrec, but with a left-to-right guarantee. So if you changed yourletrectoletrec*, it’d be okay.Now, back to my initial comment: because R5RS’s internal
defineusesletrecsemantics, even yourdefineversion of the code would be incorrect under an R5RS implementation, but it would be okay under an R6RS implementation, which hasletrec*semantics.