The idea behind all simple lists is the following:
- Make sure you can tell if you have an empty list or not.
- If it's not the empty list, you have 2 parts:
- the head of the list
- and the tail (rest of the list)
Since Java supports closures (well, kind of), one can implement lazy lists with the following trick: Instead of having a reference directly to the tail, have a reference to an Object which is able to compute the tail. Such an Object could be a Callable<ZList>
, say, if ZList
is our list type. And yes, such a closure constructed with
// assuming constructor ZList(int, Callable<ZList>);
public Callable<ZList> nextNode(final int current) {
return new Callable<ZList> {
ZList call() {
return new ZList(current, nextNode(current+1));
}
};
}
takes up some finite amount of memory. Certainly more than a singe list node, but less than many list nodes.
As for the memory requirement: It is posible to go through such an infinite list, and still have constant memory usage as long as the start of the list is stored nowhere. Because, in that case, the head node you're done with can and will be garbage collected.
If, however, you have the head of the infinite list somewhere (in a static variable, say) memory usage will increase as you traverse the list.
Because such things are painful to write in Java, there are new JVM languages like Scala (functional/OO), Clojure (List) or Frege (Haskell like) that make it easy to have such things in the JVM.
Frege may be interesting for you because a) it is non-strict (i.e. lazy) and b) it spits out java source code, hence you could study and play the java sources created by simple functional programs.
Let's start by postulating that memory is by far (dozens, hundreds or even thousands of time) more common than all other resources combined. Every single variable, object, object member needs some memory allocated to it and freed later on. For every file you open, you create dozens to millions of objects to store the data pulled out of the file. Every TCP stream goes together with an unbounded number of temporary byte strings created to be written to the stream. Are we on the same page here? Great.
For RAII to work (even if you have ready-made smart pointers for every use case under the sun), you need to get ownership right. You need to analyse who should own this or that object, who should not, and when ownership should be transferred from A to B. Sure, you could use shared ownership for everything, but then you'd be emulating a GC via smart pointers. At that point it becomes much easier and faster to build the GC into the language.
Garbage collection frees you from this concern for the by far most commonly used resource, memory. Sure, you still need to make the same decision for other resources, but those are far less common (see above), and complicated (e.g. shared) ownership is less common too. The mental burden is reduced significantly.
Now, you name some downsides to making all values garbage collected. However, integrating both memory-safe GC and value types with RAII into one language is extremely hard, so perhaps it's better to migitate these trade offs via other means?
The loss of determinism in turns out to be not that bad in practice, because it only affects deterministic object lifetime. As described in the next paragraph, most resources (aside from memory, which is plentiful and can be recycled rather lazily) are not bound to object lifetime in these languages. There are a few other uses cases, but they are rare in my experience.
Your second point, manual resource management, is nowadays addressed via a statement that does perform scope-based cleanup, but does not couple this clean up to the object life time (hence not interacting with the GC and memory safety). This is using
in C#, with
in Python, try
-with-resources in recent Java versions.
Best Answer
Modern C++ makes you not worry about memory management until you have to, that is until you need to organize your memory by hand, mostly for optimization purpose, or if the context forces you to do it (think big-constraints hardware). I've written whole games without manipulating raw memory, only worriing about using containers that are the right tool for the job, like in any language.
So it depends on the project but most of the time it's not memory management that you have to handle but only object life-time. That is solved using smart pointers, that is one of idiomatic C++ tool resulting from RAII.
Once you understand RAII, memory management will not be a problem.
Then when you'll need to access raw memory, you'll do it in very specific, localized and identifiable code, like in pool object implementations, not "everywhere".
Outside of this kind of code, you'll not need to manipulate memory, only objects lifetime.
The "hard" part is to understand RAII.