Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Language like Haskell have concept of purity. In pure function, I can't mutate any state globally. Anyway Haskell fully abstracts memory management, so memory allocation is not a problem here.

But if languages can handle memory directly like C++, it's very ambiguous to me. In these languages, memory allocation makes visible mutation. But if I treat making new object as impure action, actually, almost nothing can be pure. So purity concept becomes almost useless.

How should I handle purity in languages have memory as visible global object?

share|improve this question
    
Up till recently it was rather difficult to write functional C++ code. A good middle of the road language that tries to be both as fast as C and give you access to the metal and yet allow to declare functions as pure and references as immutable is D: dlang.org/function.html I think it would be easier to discuss this topic with D as the language at hand. –  Job Apr 9 '12 at 3:38
    
@Job Oh thank you for recommendation, but I can't choose any GC based language for my goal :( –  Eonil Apr 9 '12 at 4:28
add comment

4 Answers

up vote 0 down vote accepted

I think you're asserting too much when you say that "making new objects" being impure implies that almost nothing can be pure. If you isolate your memory allocation from your mainline logic, then the main-line logic can be made as "pure" or "impure" as the situation demands. A technique I use regularly is to define data manipulation functions that leave the this object untouched, but that take a parameter for a destination in which the results should be placed. (Often, I allow the destination to be the same as the this object.) For example,

class Countdown
{
private:
    int _CountLeft;
public:
    void init(int CountLeft) { this->_CountLeft = CountLeft; }
    int getCountLeft() const { return this->_CountLeft; }
    bool decrease(Countdown &Dest) const {
        Dest._CountLeft = this->_CountLeft - 1;
        return Dest._CountLeft > 0;
    }
};

I'm aware that this isn't idiomatic C++ (more on that in a bit), but it is a way of implementing pure functions in C++.

Now, I'll try to explain some odd features of the class:

Why use an init method, instead of a constructor?

I deliberately wanted to remove any memory management concerns from this object's specification; using a constructor or destructor introduces some functional dependence of the class's behavior on memory allocation / release.

How is this function "pure"?

It is "pure" in the sense that, if we always give a new instance of the object as a destination parameter, then the input data to any operation never changes. For example:

Countdown CountdownArray[10];
CountdownArray[0].init(5);
for(
  int Index = 0;
  Index < 10 - 1 &&
    CountdownArray[Index].decrease(CountdownArray[Index+1];
  Index++)
{
    continue;
}

Well, obviously this behavior is not "pure", exactly, but the elements of CountdownArray have not been modified since their first initialization; the decrease method does behave in a pure fashion, when given an appropriate destination parameter.

How can I manage the memory for this class effectively?

In this case, the class is written such that the Dest parameter can be the same object pointed to by this:

Countdown CD;
CD.init(5);
while(CD.decrase(CD))
{
}

This obviously makes the behavior less "pure", but also makes the generated code significantly more memory-efficient.

How does this technique interact with inheritance?

Good question. I avoid inheritance when using this technique.

Conclusion

A general-purpose computer cannot practically be "pure" - we have a finite amount of resources to work with in a computer (memory, disk space, CPU registers), and efficient use of these resources requires that they be mutable. What people mean by "purity" in computing is that we've isolated the immutable from the mutable, and the immutable state can be considered "pure". You're correct that memory allocation can have side effects on other parts of the system (e.g. by causing later allocations to fail as we run out of memory). But it can be practical to factor out the memory allocation from the code that we want to be pure.

share|improve this answer
    
MAybe abstraction layer hides memory side effect is the way to go... thanks a lot! –  Eonil May 9 '12 at 8:40
    
"A general-purpose computer cannot practically be "pure" - we have a finite amount of resources to work with in a computer ... and efficient use of these resources requires that they be mutable." This conclusion is pretty misleading. No one does real work on a Turing machines, so the fact that the hardware uses mutable memory is neither here nor there. A pure language implements a general-purpose computer just as well as an impure language, and the language is what you program in. I'm not seeing what the finiteness of resources or their efficient usage have to do with anything either. –  Doval Jun 12 at 11:26
add comment

I have to disagree with the basic premise.

C++ (to use your example, though the same would apply to C, Pascal, Ada, etc.) doesn't give any real visibility into the heap. You can attempt to allocate some memory, which might succeed or fail -- but you have no visibility into why an allocation succeeded or failed, nor what other allocations may have lead to that success/failure.

In other words, allocating some memory in one place does not have an effect that's directly visible anywhere else. Yes, it's possible one allocation could lead to the failure of another, but 1) noting in portable C++ can put those together, and 2) it's usually next to impossible to make that connection even in a non-portable way.

In the other direction, nothing in Haskell (or any other language) can change the fundamentals involved anyway. Attempting to allocate more memory than is available (even as virtual address space) will fail, regardless of the language. If the user runs another program that hogs all the memory, neither C++ or Haskell (or much of anything else) can do much about that, so an allocation that would succeed in one run may fail in another.

In fairness, I suppose I should add that even though it's not portable, many (most?) heap managers include extra functions to walk the current heap, see what blocks are allocated, etc. Yes, I suppose such a thing could be seen as breaking "purity", but my guess is that people including heap-walking in their software probably aren't bothered a lot by that (and those who really care about purity just don't use that capability.

share|improve this answer
add comment

Purity is a concept that doesn't become useless just because the programming language doesn't enforce it. Haskell may well be your best bet if you're looking for absolute assurance of purity, but that doesn't mean the concept is useless in C++. You can certainly increase the purity of code in C++, and that can certainly have a positive effect on its quality.

share|improve this answer
add comment

C++ has the capability to provide such constructs/forms.

You could implement these abstraction layers, if you choose.

To enforce this, you might start by adding const to everything, then use objects' constructors to define their state, then introduce smart pointers, then probably realize it was slow and create thread local allocators, and so on…

Apparently, GHC will also output C programs -- perhaps this output would help visualize how it can be approached using C or C++?

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.