LWN.net Logo

What, again?

What, again?

Posted May 2, 2007 9:21 UTC (Wed) by schaueho (guest, #45025)
In reply to: What, again? by ncm
Parent article: The Rise of Functional Languages (Linux Journal)

> Perhaps the greatest deficit of all these languages is their inability to
> manage resources outside the mathematical domain of the language. Toss in
> some file descriptors, sockets, database connections, or locks, and
> suddenly you're back to the Stone Age. This is a curious consequence of
> LISP's original memory management primitives, CONS and garbage collection.

Are you sure you have seen any functional language in the last five years? Perhaps you should have come out of that cage before throwing rocks. Take for instance any Common Lisp implementation (randomly chosen as an example because of your lisp bashing, not because I'm a Lisp zealot): You will find file handles, socket libraries, database access etc., typically nicely integrated. In particular, the Common Lisp condition system (think of it as an extended framework for exception handling) eases handling the nitty-gritty error situations that occur in production systems.

I think your comment is really funny, because the "rise of functional languages" also has to do with Python and Ruby, which do have a strong functional influence and are typically chosen because of their easy (scripting) integration with todays technologies.


(Log in to post comments)

What, again?

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

Yes, Common Lisp, like C, has access to file handles and sockets. However, as in C, you cannot abstract management of them. To be very precise, if you put a socket in some larger data structure, you must explicitly call some cleanup function to ensure it gets closed in a timely fashion. You cannot automate this management. You cannot write a library which invisibly embeds a socket without exposing to the user a need to call your own cleanup function. ("Finalization" is no help, and in real systems has proven actually harmful.)

Without the ability to automate such management, exception handling facilities are a cruel joke, unable to concentrate error handling in a few well-tested spots. Instead, you get "finally" clauses, hundreds or thousands of separate snippets of error handling code scattered throughout one's program, as hard to exercise as error-code returns, and as likely to be wrong.

The true cost of most languages' failure to provide the rudiments of resource management is hard to bound. It ends up touching everything.

What, again?

Posted May 3, 2007 6:34 UTC (Thu) by flewellyn (subscriber, #5047) [Link]

To be very precise, if you put a socket in some larger data structure, you must explicitly call some cleanup function to ensure it gets closed in a timely fashion. You cannot automate this management. You cannot write a library which invisibly embeds a socket without exposing to the user a need to call your own cleanup function.

AHEM. I must be imagining the existence of the WITH-OPEN-FILE macro in the Common Lisp standard, which does exactly this: automates the closing of a file, invisibly, such that the user need not be concerned with explicitly calling the CLOSE function. Within the macro body, the file is referenced by the variable you give the macro as an argument; once control leaves the body of the macro, you are guaranteed that the file has been closed. Even if code contained in the body signals an error, the file WILL close.

Under the hood, of course, the macro expands into an UNWIND-PROTECT special form, in which the code that calls the necessary OPEN function and your code to work with the file are wrapped, along with the cleanup code that CLOSEs the file, which UNWIND-PROTECT guarantees will run when the form exits, normally or otherwise.

You'll see this idiom all over the place in Common Lisp libraries of all types. For instance, sockets are not part of the standard, since it was written before Berkeley Sockets became The One True Networking Way, but every socket library I've seen for CL has a WITH-OPEN-SOCKET macro that handles things the same way. Anywhere you have a resource that needs to be handled in a body of code and then disposed of, the rule is the same: write a WITH-style macro that wraps the opening and closing code in UNWIND-PROTECT, and then call the macro intead of worrying about the explicit managment.

So you see, you CAN abstract management of non-memory resources, such that they are always correctly cleaned up. You just need proper macro support and something akin to UNWIND- PROTECT.

What, again?

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

This argument has already been disposed of elsewhere. Anyhow, it is not responsive: with-open-file is no help at all when your file descriptor is placed in a data structure returned to a caller, which is the case I described above.

The argument form used here is familiar: "Lisp is Good. Therefore, Lisp lacks nothing important. Therefore either the feature you describe is not important, or Lisp really has it after all. If in fact it is important and Lisp lacks it, let me describe some other feature which it does have, which you must agree is good." Congratulations on skipping over the middle bits. I will happily concede that with-open-file is positively jim-dandy, but not that it is an adequate substitute for the capacity to abstract management of non-memory resources.

There are sound reasons that Lisp, over the course of five decades, has failed to attract significant industrial use. Rather than engaging in apologetics, it would be better to understand industrial needs and design languages to meet those needs. C++ achieved its industrial position precisely this way. A Haskell or ML variant with destructors and without the equivalent of CONS might give C++ a run for its money. However, C++ isn't standing still. C++09 (and G++-4.x, for some small x) will support concepts, which is another fundamental advance.

What, again?

Posted May 3, 2007 7:28 UTC (Thu) by flewellyn (subscriber, #5047) [Link]

Okay, fair enough. So I slightly misunderstood your point.

Your point, it seems, is "If I do X which breaks the automatic tools we have to manage resources,
then I have to manually manage the resources! This is bad!"

My response is: so don't do that, then.

But aside from that, you've spent a lot of time saying "current languages don't offer a way to
manage resources the Right Way." What is this Right Way?

Rather than engaging in polemics, it would be better to help us understand just what it is you're
looking for in a functional language that doesn't do what you want.

What, again?

Posted May 9, 2007 6:13 UTC (Wed) by ncm (subscriber, #165) [Link]

"My response is: so don't do that, then."

I.e., don't try to write industrial code in Lisp. And I don't. Which is to say, you didn't slightly misunderstand my point; you entirely missed it.

Functional or not functional is irrelevant to the discussion. It would not be especially difficult to invent a new language with a strong type system, and destructors, as powerful as C++ but about a tenth the size. Functional semantics might make it easier to specify, and to optimize. For destructors to help, it would need to be non-garbage-collected, or it would need to offer some way to indicate that an allocation is meant to be GC'd, and prevent anything with a destructor from being allocated that way.

Block scope is the most powerful organizational method we have in programming. "With-foo" macros correctly tie object lifetime to a scope. However, they tie it to the wrong scope. Frequently the correct lifetime matches the block of some caller up the chain from the function that claimed the resource. Just a little less frequently, it's some other block entirely. Lisp offers no mechanism to express this, and neither can any other language that lacks destructors.

The consequence for exception handling is a little harder to explain, but just as large in its effect on programs. In a well-constructed C++ program, cleanup code is almost all in destructors, and exercised routinely in the course of running the program. There are no "finally" clauses, and very few "catch" clauses. In other words, in a "throw", very little code is executed that would not be run anyway. Exceptions are routed to just a few easily tested collection points. Omit destructors, and cleanup code has to be put in "finally" clauses scattered throughout the program, typically difficult or even impossible to test.

What, again?

Posted May 9, 2007 6:44 UTC (Wed) by flewellyn (subscriber, #5047) [Link]

No, I did not mean "don't write industrial code in Lisp".

I meant "don't go out of your way to break your tools".

Honestly, I think I agree with the commenter who said that you present a prejudice, not an
argument.

What, again?

Posted May 3, 2007 14:13 UTC (Thu) by jschrod (subscriber, #1646) [Link]

Your rabid polemics simply show that you don't know Common Lisp -- or O'Caml, to prevent your "you are a stupid Lisp fanatic who doesn't know a thing about Real Programming(tm)" bashing that you have so eagerly thrown at the GP and at several other posters.

If one wants them, CLOS has explicit constructors with full control over object lifetime and the ability to manually code resource management as needed. That's what MOP is for.

There are quite some technical and management problems when one wants to use CL or O'Caml for application development, but automatic or explicit resource management ain't one. And this is also not the reason why such languages aren't widely used, either -- reasons to select a programming language are most often non-technical.

What, again?

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

"Rabid polemics"? I don't recall using words like "rabid", "stupid", or "fanatic". Maybe I'm being confused with somebody else, or maybe somebody is taking objective analysis of computer language features personally.

Nobody has said Lisp makes constructors impossible, or even difficult. Nobody has said Lisp makes manual resource management impossible, or difficult. What is impossible in Lisp, as in other GC-dependent languages, is any equivalent to destructors, and (therefore) the abstraction capabilities they enable. It's a fundamental language-design choice: provide tools so users can build resource management in libraries, or build exactly one kind of resource management into the language core. The latter approach is favored in academic languages meant to impress professors, the former has become essential in a language meant for industrial use.

For easy problems, any language will do -- perhaps badly, but few notice. For large, hard problems, language choice can make the difference between success and failure. Lisp has had very few industrial successes despite a head start measured in decades. The reasons are worth investigating if you want to be responsible for industrial successes. If you don't, you have lots of company.

What, again?

Posted May 4, 2007 9:13 UTC (Fri) by dcoutts (subscriber, #5387) [Link]

I think I see the point you've been trying to make and we've mostly all been missing.

You're saying that if we have some library function which returns an object and that object embed but does not expose some expensive resource (db connection or whatever) then in a normal GC language we can't guarantee that the DB connection gets released in a timely manner.

This is kind of true. Note though that in C++ it relies on the user, the recipient of the object, to free that object it a timely manner. That is, it relies manual memory management everywhere. If the caller is using a local stack allocated variable that's probably going to be ok, but if they're dynamically allocating them then we're relying on the user to know when to free them.

In a sense we cannot abstract this anyway, since the caller really does need to know that the resource is expensive and that they should not hold onto them for too long or have too many allocated at once. Putting expensive objects inside other structures and claiming it's abstract doesn't really help, it's still an expensive object that somehow needs to be treated carefully.

In the same way, in a mostly GC'd language where for this expensive resource we have to use more careful block scoping or explicit resource release methods we get exactly the same lack of abstraction. Any object which embed this expensive object has to also use more careful resource allocation techniques. In either case, this cost is always imposed on the caller, you can't not know about the high cost of the object and get a well performing program.

The difference is that in a GC'd language it looks more explicit because for all the normal objects that do not embed expensive resources we get to use a less obtrusive mechanism. In C++ we have to pay the cost of manual memory management everywhere, so it doesn't look much worse to deal with the expensive object case.

What, again?

Posted May 4, 2007 16:28 UTC (Fri) by schaueho (guest, #45025) [Link]

Speaking of running code on object destruction, SBCL includes an extension called sb-ext:finalize, doing the obvious. I would be surprised to find that only SBCL should provide something like that (actually a quick goggle search tells me that there are all sorts of implementations of this, summed up in clocc). Of course, not part of the standard means not part of the language, and as you also point out, it's an additional hassle.

What, again?

Posted May 7, 2007 1:06 UTC (Mon) by ncm (subscriber, #165) [Link]

No, finalization is no substitute. In fact it is actively harmful. You have no way to know when a finalization routine will happen, whether it will ever happen, what thread it will happen in, what order a series of them will be called in, or what else may be going on when they fire off. Competent Java shops typically forbid finalization except for debugging purposes, to help catch missed manual close operations.

What, again?

Posted May 7, 2007 0:59 UTC (Mon) by ncm (subscriber, #165) [Link]

No, I'm afraid you have completely missed the point.

In fact, in C++, the recipient of the object does not need to free it in a timely manner. Libraries do not rely on manual memory management everywhere. As I have noted elsewhere in this thread, it has been many years since I coded a "delete" statement. Where is this "cost of manual memory management" I am supposed to be paying everywhere? C++ coders don't pay any such cost.

In fact you can, in C++, abstract management of scarce resources (anyway). It may be necessary for the caller to know an expensive resource is contained, but not for the caller to explicitly call a "close" or "free" function to release it. In fact, most such objects' lifetimes are bounded by some scope, but (and this is important; stop and consider carefully) not typically the scope in which they were created or claimed. It is trivial to arrange for the equivalent of a close to happen automatically in C++, but it remains entirely impossible in any typical GC language, particularly including Lisp and all its descendants.

Let me repeat that: Turing-completeness notwithstanding, this common need in industrial programming is one that Lisp cannot address at all, as a direct result of a fundamental weakness in the core language design. That fundamental weakness cannot be resolved so long as CONS and GC remain in the core language. Other languages that have adopted Lisp's CONS and GC (or their equivalent) suffer the same weakness, whatever their other strengths.

What, again?

Posted May 4, 2007 6:59 UTC (Fri) by schaueho (guest, #45025) [Link]

> Yes, Common Lisp, like C, has access to file handles and sockets.
> However, as in C, you cannot abstract management of them. To be very
> precise, if you put a socket in some larger data structure, you must
> explicitly call some cleanup function to ensure it gets closed in a
> timely fashion.

This part confirms my guess that you haven't worked with functional languages in the last five years. In Common Lisp you would write a macro that wraps around any access to your resource and ensures that whatever you need to do after some functionality gets executed (i.e. clean whatever in whatever way up). This *is* the abstraction you would provide to the user of your library/whatever. Just because it's different from what you would do in an imperative language (or make that a "language with explicit memory managment") like C doesn't mean it isn't possible.

> Without the ability to automate such management, exception handling
> facilities are a cruel joke, unable to concentrate error handling in a
> few well-tested spots. Instead, you get "finally" clauses, hundreds or
> thousands of separate snippets of error handling code scattered
> throughout one's program, as hard to exercise as error-code returns, and
> as likely to be wrong.

Take a look at the Common Lisp condition system, it will broaden your view
on "exception" handling a lot.

No

Posted May 9, 2007 5:37 UTC (Wed) by ncm (subscriber, #165) [Link]

1. You haven't been reading carefully.

2. You are mistaken; Lisp does in fact lack the important capability I identified. Of course it is only important in industrial work, and not in academic demonstrations. You haven't missed it. I certainly would.

3. Your attitude is common among Lisp fans, and suffices to explain why Lisp has not gained a foothold in industrial programming despite fifty years' promotion in academia.

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