I've looked at everything I can find about letrec, and I still don't understand what it brings to a language as a feature. It seems like everything expressible with letrec could just as easily be written as a recursive function. But are there any reasons to expose letrec as a feature of a programming language, if the language already supports recursive functions? Why do several languages expose both?
I get that letrec might be used to implement other features including recursive functions, but that's not relevant to why it should itself be a feature. I've also read that some people find it more readable than recursive functions in some lisps, but again this is not relevant, because the designer of the language can make an effort to make recursive functions readable enough to not need another feature. Finally, I've been told that letrec makes it possible to express some kinds of recursive values more succinctly, but I have yet to find a motivating example.
TL;DR:
defineisletrec. This is what enables us to write recursive defintions in the first place.Consider
To what entity does the name
factinside the body of this definiton refer? Withlet foo = val,valis defined in terms of already known entities, so it can't refer tofoowhich is not defined yet. In terms of scope this can be said (and usually is) that the RHS of theletequation is defined in the outer scope.The only way for the inner
factto actually point at the one being defined, is to useletrec, where the entity being defined is allowed to refer to the scope in which it is being defined. So while causing evaluation of an entity while its definition is in progress is an error, storing a reference to its (future, at this point in time) value is fine -- in the case of usingletrecthat is.The
defineyou refer to, is justletrecunder another name. In Scheme as well.Without the ability of an entity being defined to refer to itself, i.e. in languages with non-recursive
let, to have recursion one has to resort to the use of arcane devices such as the y-combinator. Which is cumbersome and usually inefficient. Another way is the definitions likeSo
letrecbrings to the table the efficiency of implementation, and convenience for a programmer.The quesion then becomes, why expose the non-recursive
let? Haskell indeed does not. Scheme has bothletrecandlet. One reason might be for completeness. Another might be a simpler implementation forlet, with less self-referential run-time structures in memory making it easier on the garbage collector.You ask for a motivational example. Consider defining Fibonacci numbers as a self-referential lazy list:
With non-recursive
letanother copy of the listfibswill be defined, to be used as the input to the element-wise addition functionadd. Which will cause the definition of another copy offibsfor this one to be defined in its terms. And so on; accessing the nth Fibonacci number will cause a chain of n-1 lists to be created and maintained at run-time! Not a pretty picture.And that's assuming the same
fibswas used fortail fibsas well. If not, all bets are off.What is needed is that
fibsuses itself, refers to itself, so only one copy of the list is maintained.