LWN.net Logo

What, again?

What, again?

Posted May 1, 2007 23:37 UTC (Tue) by peterh (subscriber, #4225)
In reply to: What, again? by ncm
Parent article: The Rise of Functional Languages (Linux Journal)

> This isn't because C++ is perfect, or close, but because it does offer
> the tools they need. To be very specific, it offers destructors.

What, _exactly_ do you get from having destructors (over, say, java-style finalizers)? I'm looking for a precise argument here as to why the absence of this language feature makes it hard to write large, real-world programs.

Really, these days you need a very good reason to write code in C or C++. The benefits of safe languages are very compelling. There are reasons to use C/C++ and friends, they just don't apply to the vast majority of code that people write (i.e. not operating system kernels, nor that very small proportion of code where small constant-factor performance differences matter).

I'm afraid I simply don't follow your statement about "[failing] to provide the tools needed to manage that [resource]". It's difficult to understand such a claim when the current #1 language (by the TIOBE index) Java, is so heavily influenced by ideas from the programming languages research community (type-safety, garbage collection, generics (aka. type-classes), ... largely functional language concepts). Clearly a large number of programmers manage to get their work done in such an environment.


(Log in to post comments)

What, again?

Posted May 2, 2007 0:58 UTC (Wed) by bronson (subscriber, #4806) [Link]

What does a destructor provide that finalize() doesn't? That's easy: a definite time that the cleanup code is called. This argument is ages old. Briefly:
    for(log files) {
        File f;
        f.open(file name);
        f.quick_scan();
    }
In C++, you will only have a single fd in flight because the destructor is called and the file is closed the moment the block is exited. In Java (older versions; I haven't used Java recently), you'd typically bomb out after a few thousand iterations because you exhaust your file descriptors before a garbage collection cycle can be run.

Most resources are of such a limited quantity that you need to know with confidence exactly when they are released. Destructors provide this, finalizers don't.

For instance, do you see the problem with the following code? It's only a problem if your language uses finalizers.

   try {
     FileReader f = new FileReader(filename);
     process(f);
     r.close();
   } catch(IOException e) {
     ...
   }

What, again?

Posted May 2, 2007 1:34 UTC (Wed) by peterh (subscriber, #4225) [Link]

Yes, but you have _exactly_ the same problem in a language with explicit deallocation and destructors.

If you use finalizers to free resources, you'll have these sorts of problems. Use a free_my_resources() method for that. If you forget to call it, you'll leak the resources (but the finalizer could check for that, at least limiting the damage). If, on the other hand, you forget to call "delete" on the object, you leak the resources and the object itself. So you have to do something explicit either way, and without garbage collection the damage is worse (you have a memory leak into the bargain).

Not quite sure how that's an improvement, myself.

There is an exception here --- if your object is allocated on the stack (which Java forbids). This does get you a kind of "auto-destruction" semantics. But is this a common case?

What, again?

Posted May 2, 2007 3:42 UTC (Wed) by ncm (subscriber, #165) [Link]

It has been almost ten years since I coded a "delete" statement in C++. I can't forget to write them because I never needed to write them in the first place. How does memory get freed? Destructors free it. I don't write calls to destructors, because the compiler writes those calls itself.

That's what is meant by "encapsulating resource management in libraries": all the work is coded in the library, and then you just use it. There is no value at all in the language providing "memory-safety" when the language is powerful enough to allow it to be encoded in libraries in exactly the forms needed. A language not powerful enough to encode resource management in a library looks useless to me.

Finalizers are little more than a cruel joke, and do not merit discussion in this context.

What, again?

Posted May 2, 2007 4:41 UTC (Wed) by foom (subscriber, #14868) [Link]

...just so long as you never have circular references.

What, again?

Posted May 2, 2007 17:06 UTC (Wed) by dcoutts (subscriber, #5387) [Link]

So you are exclusively using blocked scoped resources or smart pointers. With smart pointers you either are using ref counting or single owner semantics. Single owner is very limited, it doesn't allow you to build shared structures where there are multiple ways of reaching a value. If you're using ref counting it's not too bad, though you can't ever build cyclic data structures and you have to keep track of who is holding onto references or you get space & resource leaks. This last point is true of GC'ed languages too, forgetting that some object holds a reference to some resource causes space leaks.

In my experience, essentially all the times where we have to be concerned with use of a precious resource there are very clear bounds where the resource is needed. In almost all of these cases simple block scoping of the resource is sufficient. As you say there are some cases where we want more flexible techniques like pools but again these can be implemented in any language GC'd or not.

Sure the Java example where it does not let you do any manual resource management and only closes files via a finaliser is silly. All it needs is a .close() method or something and then in a nice functional language with cheap abstractions you don't even need to remember to call .close() as (like in my earlier example) you can write functions which encapsulate the resource usage patterns like allocate, use, release.

What, again?

Posted May 2, 2007 18:26 UTC (Wed) by bronson (subscriber, #4806) [Link]

> if your object is allocated on the stack (which Java forbids)...

If you didn't notice, I'm using Java finalizers and its lack of automatic objects as an example of broken design.

> ...this does get you a kind of "auto-destruction" semantics. But is this a common case?

In C++, absolutely. It couldn't be more common.

I feel like I've answered your question but you're refusing to acknowledge it. Just because Java blew it with finalizers, that doesn't mean that destructors are broken too. Did you understand the second example in my post? I'm happy to explain further if you'd like.

What, again?

Posted May 2, 2007 1:23 UTC (Wed) by djabsolut (guest, #12799) [Link]

these days you need a very good reason to write code in C or C++

A very pointed statement. Some counter arguments: Firstly, C and C++ are two different languages. The latter just happens to contain a large subset of the former. Secondly, C++ can be a very sharp tool, but a coder can easily restrict him/herself to only use safe programming practices and idioms in C++ (one example: ignoring all the nasty parts of C). Thirdly, the "truckload of libraries" advantage of languages such as Python has been more than noticed by the folks working on the next version of C++. Many of the Boost libraries are candidates for inclusion in the next standard (in fact, there is already a few of them in the TR1 extensions of C++). Due to C++'s templates and the ability to overload operators, some of the libraries have in effect extended the language already. To put it another way: the amount of work done per line of source code has increased considerably. Fourthly, apart from numerical applications where Fortran may still be faster, it is hard to beat the speed of C++ programs.

What, again?

Posted May 2, 2007 3:31 UTC (Wed) by ncm (subscriber, #165) [Link]

Actually C++ is now beating FORTRAN soundly in numerics, too. This is a consequence of its template system, which allows domain-specific optimizations to be defined in libraries, and applied at compile time. Even back in 1995, Todd Veldhuizen wrote a C++ library that beat IBM's specialized FORTRAN by 40% on IBM's own vector machine running fluid-dynamic simulations.

The C++ numerics community has not stood still since then. A current example of such a library is VSIPL++, which can apply MPI underneath to automatically parallelize array operations. It's published under GPL, by the way, so you can find out how.

What, again?

Posted May 2, 2007 17:16 UTC (Wed) by dcoutts (subscriber, #5387) [Link]

Yeah, adding domain-specific optimisations into libraries is cool stuff and really practical. We do this with the Haskell compiler GHC which allows you to stick equational rewrite rules into a library and then the compiler applies them to users code. In effect it allows you to program the optimiser letting the library author apply domain-specific knowledge to direct how to compile user's code.

We do this for example to fuse array operations, combining mutliple passes over arrays into a single pass and eliminating intermediate temporary arrays. This allows users to write in a higher level modular style but then we can compile this down to fast low level code.

Compile-time typing good.

Posted May 2, 2007 18:10 UTC (Wed) by ncm (subscriber, #165) [Link]

Yes, Haskell has a very powerful type system. (Of course that's nothing to do with its being a functional language, so we are drifting off topic a bit, but what the hell.) VSIPL++, by the way, also fuses array operations, probably by very similar means. Recently it demonstrated a 3x FFT performance on IBM Cell vs. IBM's own library and custom compiler.

I met somebody back in Massachusetts working on something he called Eager Haskell. People laugh, but it's much faster than regular Haskell, making it practical, at least for pure computational work. Probably Darcs should be rewritten in Eager Haskell.

Somebody else demonstrated a Haskell compiler as a C++ library. It compiles (rather slowly) to fast native code.

What's practical?

Posted May 3, 2007 0:14 UTC (Thu) by shapr (subscriber, #9077) [Link]

What's practical?

Some Haskell applications that I consider to be practical are: a Quake clone Frag, a fast as Apache webserver HWS, and an operating system named House.

You mentioned darcs already, it also seems practical to me.

You also mentioned that Haskell compilers produce fast native code, so what about Haskell do you perceive to be impractical?

What's practical?

Posted May 3, 2007 18:03 UTC (Thu) by ncm (subscriber, #165) [Link]

Haskell's garbage collector and the language primitives that depend on it makes it impossible to implement the features needed to abstract management of non-memory resources. Its inability to abstract resource management also makes it impossible for exception facilities to safely centralize error handling. In this it is like other languages that have failed, and will continue to fail, to unseat C++ from serious industrial applications. That's unfortunate because C++ needs serious competition.

By "practical", in context, I meant "practical for industrial use". For pure computation Haskell (well, Eager Haskell) verges on the practical already.

Looks practial to me.

Posted May 4, 2007 0:09 UTC (Fri) by shapr (subscriber, #9077) [Link]

Haskell's garbage collector and the language primitives that depend on it makes it impossible to implement the features needed to abstract management of non-memory resources.
I think this is incorrect.
First of all, non-memory resource management is already abstracted. File handles, sockets, etc are garbage collected when they are no longer reachable.
Second of all, if you want to be able to explicitly de-allocate a resource before the GC reaches it, you can use an RAII-style wrapper, like withSocket or withHandle.

Haskell doesn't really need explicit destructor support in the language. Because they can be written in a library, they don't need to be part of the core.
Its inability to abstract resource management also makes it impossible for exception facilities to safely centralize error handling.
I'm not really sure what you mean here. Haskell exceptions can be caught anywhere further up in the scope. That means you can catch an exception thrown deep in the code and handle it in your top level code. That sounds like centralized error handling to me. Perhaps I misunderstood?

It sounds like your concern is about putting resources inside other structures (assuming proper use of those structures) and getting semi-automatic management, with an option to have more explicit management.
I've already mentioned GC and RAII-style (block scope) destructors. For other custom schemes (pools, regions, etc), you can use monads.
By "practical", in context, I meant "practical for industrial use".
Haskell is already used in the industry. What's your definition of industrial use?
For pure computation Haskell (well, Eager Haskell) verges on the practical already.
In my experience, non-strictness in Haskell dramatically simplifies the logic of a program, I wouldn't want to return to writing strict languages. Why do you think non-strictness is impractical?

Boost?

Posted May 2, 2007 10:54 UTC (Wed) by scripter (subscriber, #2654) [Link]

The upcoming inclusion of Boost in the C++ standard is admirable. This is coming at least 10 years later than it should have. On the other hand, it's taken time to standardize C++ and get the various compiler implementations up to date.

Eleven years ago, I started using CPAN. For most projects, it's not so much important what language is used as the community, the libraries and the tools that surround the language. It's no wonder that languages other than C++ have seen such dramatic use over the past decade.

I'm starting to use Boost, and it has been worthwhile.

Copyright © 2008, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds