Do all functional languages use garbage collection

functional programminggarbage-collectionprogramming-languages

Is there a functional language which allows to use stack semantics – automatic deterministic destruction at the end of the scope?

Best Answer

Not that I know of, though I'm no functional programming expert.

It seems pretty difficult in principle, because values returned from functions may contain references to other values that were created (on the stack) within the same function, or might just as easily have been passed in as a parameter, or referenced by something passed in as a parameter. In C, this issue is dealt with by allowing that dangling pointers (or more precisely, undefined behaviour) may occur if the programmer doesn't get things right. That's not the kind of solution that functional language designers approve of.

There are potential solutions, though. One idea is to make the lifetime of the value a part of the type of the value, along with references to it, and define type-based rules that prevent stack-allocated values from being returned from, or referenced by something returned from, a function. I've not worked through the implications, but I suspect it would be horrible.

For monadic code, there's another solution which is (actually or almost) monadic too, and could give a kind of automatically deterministically-destructed IORef. The principle is to define "nesting" actions. When combined (using an associative operator), these define a nesting control flow - I think "XML element", with the left-most of the values providing the outer begin-and-end-tag pair. These "XML tags" are just defining ordering of monadic actions at another level of abstraction.

At some point (at the right hand side of the chain of associative composition) you need some kind of terminator to end the nesting - something to fill the hole in the middle. The need for a terminator is what probably means the nesting composition operator isn't monadic, though again, I'm not entirely sure as I haven't worked through the details. As all applying the terminator does is convert a nesting action into effectively a composed normal monadic action, maybe not - it doesn't necessarily affect the nesting composition operator.

Many of these special actions would have a null "end-tag" step, and would equate the "begin-tag" step with some simple monadic action. But some would represent variable declarations. These would represent the constructor with the begin-tag, and the destructor with the end-tag. So you get something like...

act = terminate ((def-var "hello" ) >>>= \h ->
                 (def-var " world") >>>= \w ->
                 (use-val ((get h) ++ (get w)))
                )

Translating to a monadic composition with the following execution order, each tag (not element) becoming a normal monadic action...

<def-var val="hello">  --  construction
  <def-var val=" world>  --  construction
    <use-val ...>
      <terminator/>
    </use-val>  --  do nothing
  </def-val>  --  destruction
</def-val>  --  destruction

Rules like this could allow C++-style RAII to be implemented. The IORef-like references cannot escape their scope, for similar reasons to why normal IORefs can't escape the monad - the rules of the associative composition provide no way for the reference to escape.

EDIT - I nearly forgot to say - there's a definite area I'm unsure about here. It's important to ensure that an outer variable can't reference an inner one, basically, so there must be restrictions one what you can do with these IORef-like references. Again, I haven't worked through all the details.

Therefore, construction could e.g. open a file which destruction closes. Construction could open a socket which destruction closes. Basically, as in C++, the variables become resource managers. But unlike C++, there are no heap-allocated objects that cannot be automatically destructed.

Although this structure supports RAII, you still need a garbage collector. Although a nesting action can allocate and free memory, treating it as a resource, there's still all the references to (potentially shared) functional values within that chunk of memory and elsewhere. Given that the memory could be simply allocated on the stack, avoiding the need for a heap free, the real significance (if there is any) is for other kinds of resource management.

So what this achieves is to separate RAII-style resource management from memory management, at least in the case where RAII is based on simple nesting scope. You still need a garbage collector for memory management, but you get safe and timely automatic deterministic cleanup of other resources.