As I've been learning LISP over the last 6 months or so, I've been blown away by its power and grace. But there is one thing that has been bothering me, but it may just be my C++/Java approach.
Programmers wanting to make their code solid often use Design By Contract to assert input and output contracts on functions. I see LISP has this, and that's great.
But, Java and C++, by their nature, can perform some basic contract enforcement at compile time, rather than waiting for runtime. Specifically, the compiler will bleat for violations of access (public/private), argument count, and argument type.
LISP compilers seem to throw warnings for argument count, but that's it.
I would venture that most C++/Java programmers *like* type safety and see it as a real advantage - they are warned of stupid mistakes at compile time, rather than having to vette them out at runtime. To my mind, C++/Java programmers gain the side effect of a simple preconditional contract on their arguments by using types. And, bonus for them, that contract is verified at compile time, rather than at runtime.
Does LISP have any way of doing this? I'm not proposing abusing type specifications to achieve this, but is there anyway to have simple pre and post conditional contracts verified outside of runtime?
I would love a system to be able to specify pre & post conditional contracts, class invariants, etc. and have them all checked by simply issuing a command at the prompt. While I'm fantasizing, in an ideal system, contracts that are 100% verifiable would be taken out of the code (why slow it down?), and the rest would be converted to assert clauses to continue runtime monitoring.
The current thread with the subject "Using type declarations in safety checks" is discussing exactly this topic. I'd post a reference, but I can't figure out how to get message IDs out of Google Groups.
cperk...@medialab.com (Chris Perkins) writes: > As I've been learning LISP over the last 6 months or so, I've been > blown away by its power and grace. But there is one thing that has > been bothering me, but it may just be my C++/Java approach.
> Programmers wanting to make their code solid often use Design By > Contract to assert input and output contracts on functions. I see > LISP has this, and that's great.
> But, Java and C++, by their nature, can perform some basic contract > enforcement at compile time, rather than waiting for runtime. > Specifically, the compiler will bleat for violations of access > (public/private), argument count, and argument type.
> LISP compilers seem to throw warnings for argument count, but that's > it.
> I would venture that most C++/Java programmers *like* type safety and > see it as a real advantage - they are warned of stupid mistakes at > compile time, rather than having to vette them out at runtime. To my > mind, C++/Java programmers gain the side effect of a simple > preconditional contract on their arguments by using types. And, bonus > for them, that contract is verified at compile time, rather than at > runtime.
> Does LISP have any way of doing this? I'm not proposing abusing type > specifications to achieve this, but is there anyway to have simple pre > and post conditional contracts verified outside of runtime?
> I would love a system to be able to specify pre & post conditional > contracts, class invariants, etc. and have them all checked by simply > issuing a command at the prompt. While I'm fantasizing, in an ideal > system, contracts that are 100% verifiable would be taken out of the > code (why slow it down?), and the rest would be converted to assert > clauses to continue runtime monitoring.
> Chris > Media Lab, Inc. / As Is Software, Inc.
-- Thom Goodsell tgoods...@cra.com Scientist (617) 491-3474 x574 Charles River Analytics http://www.cra.com/
cperk...@medialab.com (Chris Perkins) writes: > As I've been learning LISP over the last 6 months or so, I've been > blown away by its power and grace. But there is one thing that has > been bothering me, but it may just be my C++/Java approach.
> Programmers wanting to make their code solid often use Design By > Contract to assert input and output contracts on functions. I see > LISP has this, and that's great.
> But, Java and C++, by their nature, can perform some basic contract > enforcement at compile time, rather than waiting for runtime. > Specifically, the compiler will bleat for violations of access > (public/private), argument count, and argument type.
> LISP compilers seem to throw warnings for argument count, but that's > it.
> I would venture that most C++/Java programmers *like* type safety and > see it as a real advantage - they are warned of stupid mistakes at > compile time, rather than having to vette them out at runtime. To my > mind, C++/Java programmers gain the side effect of a simple > preconditional contract on their arguments by using types. And, bonus > for them, that contract is verified at compile time, rather than at > runtime.
> Does LISP have any way of doing this? I'm not proposing abusing type > specifications to achieve this, but is there anyway to have simple pre > and post conditional contracts verified outside of runtime?
> I would love a system to be able to specify pre & post conditional > contracts, class invariants, etc. and have them all checked by simply > issuing a command at the prompt. While I'm fantasizing, in an ideal > system, contracts that are 100% verifiable would be taken out of the > code (why slow it down?), and the rest would be converted to assert > clauses to continue runtime monitoring.
Most implementations seem not to do _vast_ amounts of "contract verification," with CMUCL being the notable exception.
Supposing you write a function that begins:
(defun db-put (db key datum &key (start 0) end db-start db-end (transaction *transaction*) (flags 0)) (declare (type db db) (string key datum) (type (or transaction null) *transaction*))
CMUCL does some validation, when you run COMPILE-FILE, to see if arguments db, key, datum, and *transaction* are used, internally, in a manner conforming with those declarations.
Supposing I set up a _call_ that doesn't conform, as with:
(db-put "db" "key" "datum")
The compiler gripes back at me thusly:
In: DB-PUT "db" (DB-PUT "db" "key" "datum") Warning: This is not a (VALUES &OPTIONAL DB &REST T): "db"
In many cases, further type inference is done by CMUCL. For instance, it is very likely going to infer that the FLAGS parameter is a NUMBER, based both on the default, and on the sorts of functions that get applied to FLAGS within the function.
That's one direction whereby some validation takes place.
Another would be via CLOS. If you use DEFCLASS to define types, and DEFMETHOD to declare methods to apply to those types, then type checking gets done quite automatically any time code is written and compiled. Invalid applications of methods often can be detected at compile time, which should provide a fair bit of what you're after.
All that being said, it's often more useful to write code in a really generic manner, not caring about types, and then tighten things down later _if it proves necessary_.
That's probably as much as you're going to get. If you want Eiffel-style DBC, then perhaps what you want to use is in fact Eiffel. If you want ML-style static type checking, then you may want to use ML. Expecting Lisp to conform to identical design criteria can't be expected to be reasonable. -- (reverse (concatenate 'string "moc.enworbbc@" "enworbbc")) http://www.cbbrowne.com/info/linux.html Rules of the Evil Overlord #231. "Mythical guardians will be instructed to ask visitors name, purpose of visit, and whether they have an appointment instead of ancient riddles. <http://www.eviloverlord.com/>
Not exactly on point, but see Mitsubishi Electric Research Labs TR 91-04 "Some Useful Lisp Algorithms: Part 1" by Richard C. Waters, where he gives an implementation for automatic regression testing of programs (post compile), and a way to determine how good your test coverage is by analyzing your program source.
The most immediate problem with doing things exactly as you suggest is just that other than CMU Common Lisp, nobody I'm aware of has implemented good type checking for lisp programs. I think CMU's dates back to spice lisp, so presumably you can get the code, and nothing prevents you from writing your own version of defun to wire it in. (There are one or more PD code walkers).
An alternative is just to write your lisp functions to correctly handle types that don't match their expected arguments. Robust code is better than contracted code, for most purposes. Ah, the metaphor!
On 11/20/01 2:12 PM, in article ¿y´RTICLE], "Chris Perkins"
<cperk...@medialab.com> wrote: > As I've been learning LISP over the last 6 months or so, I've been > blown away by its power and grace. But there is one thing that has > been bothering me, but it may just be my C++/Java approach.
> Programmers wanting to make their code solid often use Design By > Contract to assert input and output contracts on functions. I see > LISP has this, and that's great.
> But, Java and C++, by their nature, can perform some basic contract > enforcement at compile time, rather than waiting for runtime. > Specifically, the compiler will bleat for violations of access > (public/private), argument count, and argument type.
> LISP compilers seem to throw warnings for argument count, but that's > it.
> I would venture that most C++/Java programmers *like* type safety and > see it as a real advantage - they are warned of stupid mistakes at > compile time, rather than having to vette them out at runtime. To my > mind, C++/Java programmers gain the side effect of a simple > preconditional contract on their arguments by using types. And, bonus > for them, that contract is verified at compile time, rather than at > runtime.
> Does LISP have any way of doing this? I'm not proposing abusing type > specifications to achieve this, but is there anyway to have simple pre > and post conditional contracts verified outside of runtime?
> I would love a system to be able to specify pre & post conditional > contracts, class invariants, etc. and have them all checked by simply > issuing a command at the prompt. While I'm fantasizing, in an ideal > system, contracts that are 100% verifiable would be taken out of the > code (why slow it down?), and the rest would be converted to assert > clauses to continue runtime monitoring.
* Chris Perkins | But, Java and C++, by their nature, can perform some basic contract | enforcement at compile time, rather than waiting for runtime. | Specifically, the compiler will bleat for violations of access | (public/private), argument count, and argument type.
They also have a prohibitively expensive compilation process compared to Common Lisp. This is _why_ it is important in those languages to make sure that runtime is a less expensive debugging environment. Comparisons of language tend to forget their contexts and evolution completely, and take one of these for granted while the other is treated like a stranger in a strange land. This is why you should strive to forget where you came from and try to figure out how your new language came to be and why what it does is natural in its environment.
| I would venture that most C++/Java programmers *like* type safety and see | it as a real advantage - they are warned of stupid mistakes at compile | time, rather than having to vette them out at runtime.
That is because so many things are mistakes that no human being in his right mind can be expected to figure out are mistakes. Languages that are so hard only compilers (that are so hard to write that no human being in his right mind can do it) can figure out whether a program is correct, should simply not be used by human beings. I happen to think Java is pretty good this way while C++ is absolutely insane.
| To my mind, C++/Java programmers gain the side effect of a simple | preconditional contract on their arguments by using types. And, bonus | for them, that contract is verified at compile time, rather than at | runtime.
A less elaborate contract can actually be remembered and understood.
| Does LISP have any way of doing this? I'm not proposing abusing type | specifications to achieve this, but is there anyway to have simple pre | and post conditional contracts verified outside of runtime?
I think you should re-examine the reasons you are obsessing against run-time. Run-time may hurt in C++ because debugging is so hard, but learning from pain is not a very good way to learn the right thing. (The only thing that appears to work is to make it painful _not_ to think, because it is appears to be painful to many people to think.) Because you break to the debugger in Common Lisp and you can debug much more accurately when you do not compile your Common Lisp code, so the compiler is actually even less important than it might appear to be.
| I would love a system to be able to specify pre & post conditional | contracts, class invariants, etc. and have them all checked by simply | issuing a command at the prompt. While I'm fantasizing, in an ideal | system, contracts that are 100% verifiable would be taken out of the code | (why slow it down?), and the rest would be converted to assert clauses to | continue runtime monitoring.
One of the things you learn from writing serious Common Lisp programs is that you never take out the "checks and balances" part, because it does not slow the program down appreciably (use a profiler to find out where your performance is less than optimal -- it is not where you think it is, and certainly not in execution safety, which turns into crashes and long debug cycles when you remove them, reduce the safety setting, and ask for higher speed optimization, and all errors were not taken out of the code) and because even if you test your own code, you make it harder for others (including yourself) to use it, whether in that "reuse" fashion, or when modifying the program sometime later on.
Coming from the C++ world, it can take a while to get used to programs that do not crash and burn. Likewise, returning from Common Lisp to C++ is _amazingly_ painful.
/// -- Norway is now run by a priest from the fundamentalist Christian People's Party, the fifth largest party representing one eighth of the electorate. -- Carrying a Swiss Army pocket knife in Oslo, Norway, is a criminal offense.
Erik Naggum <e...@naggum.net> writes: > One of the things you learn from writing serious Common Lisp programs is > that you never take out the "checks and balances" part, because it does > not slow the program down appreciably (use a profiler to find out where > your performance is less than optimal -- it is not where you think it is,
This is a lesson that dates from the early 1970's, AFAICT. I'm completely amazed at how many programmers need to be constantly reminded of this.
> and certainly not in execution safety, which turns into crashes and long > debug cycles when you remove them, reduce the safety setting, and ask for > higher speed optimization, and all errors were not taken out of the code)
Well, unless it is in the execution safety. But then it would only be in the inner loops. Which, if you find your code doing something stupid, you carefully arrange for the code around the loop to ensure the safety of the inner loop, comment this prominently, and locally turn speed all the way up and safety all the way down. There's really no need to do this anywhere else, though.
-- /|_ .-----------------------. ,' .\ / | No to Imperialist war | ,--' _,' | Wage class war! | / / `-----------------------' ( -. | | ) | (`-. '--.) `. )----'
> They also have a prohibitively expensive compilation process compared to > Common Lisp. This is _why_ it is important in those languages to make > sure that runtime is a less expensive debugging environment. Comparisons > of language tend to forget their contexts and evolution completely, and > take one of these for granted while the other is treated like a stranger > in a strange land. This is why you should strive to forget where you > came from and try to figure out how your new language came to be and why > what it does is natural in its environment.
I'm not necessarily advocating type specifications in Lisp. I'm simply trying to write better and more solid code, which is why I have come to Lisp in the first place. I don't necessarily care to introduce type specifications to Lisp, but I would be interested in upstream (not runtime) verification of simple and complex contracts.
The sooner I can discover a typo or an error, the better. I believe this is true for any language.
> Coming from the C++ world, it can take a while to get used to programs > that do not crash and burn.
While it is true that I don't have to restart my machine when I miscode in Lisp, I still "crash and burn" - I incorrectly use a form - I pass arguments in the wrong order, etc. Little things that haunt every programmer - even Lispers. And, I assert, little things that *could* be detected by my language or tool, instead of me, my debugger, or my users.
But, as has been suggested to me, I will read up on CLOS, I will check Richard Waters' article on regression testing, and I'll muck about a bit.
On an aside, I've always felt that Design Patterns, DBC, and UML are, at an idealogical level, fundamentally tied together. They are, in a sense, programmers struggling to find a "form" or "description" of both the shape of their code and the code itself. Nowadays, when I think about that struggle I think to myself "Hmmmmm...that sounds like the type of problem Lisp would be well suited to solve"
I sent that last message before I was done when I was trying to fix my ID. cp is me, Chris Perkins.
> Nowadays, when I think about that > struggle I think to myself "Hmmmmm...that sounds like the type of problem > Lisp would be well suited to solve"
Let me clarify: Lisp seems like it could solve that problem by someone actively pursuing it and writing a solution. Merely using Lisp does not solve the problem (though it is a step in the right direction).
Chris Perkins Media Lab, Inc. / As Is Software, Inc.
cp wrote: > While it is true that I don't have to restart my machine when I miscode in > Lisp, I still "crash and burn" -
No, you just crash, not necessarily burn. It's more like that simulator in the TV ad where the guy driving the beer truck goes off a cliff because he is eyeballing a simulated blonde bombshell. The instructor gives you hell, rewinds the sim to before the babe comes into view, you keep yer eyes on the road for the most part.
I have spent hours working on a new grid widget without ever closing the main app window. I just hit the button that leads to the widgets creation, back out and start again to see the effect of any changes. If in a rare case I am just changing the paint function, all I need do is cover uncover the widget.
Even if I crash (the subject at hand), I do not burn. I poke around sometimes for a few minutes or even an hour, make all the changes I want, recompile--then look back at the backtrace to see how far back to restart to pick up the change.
Sometimes I cannot because I use Semaphors a lot and these are structures that arrange for a slot to be efficiently envalued by a formula--if I die in a formula, fixing the formula in the source does not change the lambda function that died, so I just "burn" the window and re-run. without linking.
I once ported Semaphors to C++ (and Java) and I indeed experienced the advantages of having the compiler dopeslap me around, but smarter folks than me say I cannot have it both ways--well, Dylan is a stab at that, I guess. And if I have to choose, well...
as you say, we are all trying to figure out the best way to program these things. i see a parallel between Lisp and the mountaineering technique of Reinhold Messner, who subsituted speed for stuff (gear, food, oxygen) to conquer big peaks. Instead of seige tactics in which teams of climbers went up and down ferrying supplies between higher and higher camps, and he is partner just went for it. This meant climbing unroped on stuff that would have me pissing in my pants. Messner's thinking was that usually it's the weather that kills climbers, and seige tactics ineluctably put one on the mountain longer.
So basically Messner used skill and an insane degree of conditioning to maximize speed and minimize exposure to the crucial hazard, weather. And that approach is incompatible with seige climbing.
* Chris Perkins | I'm not necessarily advocating type specifications in Lisp.
I do not follow you. Type specifications are already available to you in Common Lisp. Why use an inferior Lisp without them?
Or do you mean that you want static type checking? That you cannot get very easily.
Or do you mean that you do not want to add type declarations in the true sense of a declaration -- you _declare_ its type? Why not? If you have knowledge you withhold from the compiler, that can only be your loss.
| The sooner I can discover a typo or an error, the better. I believe this | is true for any language.
I disagree, probably to your surprise. The earlier an error is detected, the _firmer_ the determination that it is an error is, and the simpler the world must be in which statements are made. For instance, we have the option in Common Lisp of redefining a function between the definition the compiler knew about at compile-time of a caller and the actual time it was called. This would require a total re-compile in languages that consider such things an error with no option to negotiate. In the long history of programming languages, indeed human history, there are people who insist that they know what is true and good, and who seek to limit other people's opportunity to find out what other things are true and good. These generally call anything they disagree with "errors" and make people angry at their compilers or computers. Then there are people who only think they know what is wrong, and while they have their own ideas about what is true and good, they only seek to limit other people's opportunity to do wrong and harmful things. These generally do not think things are errors unless they are _clearly_ wrong and harmful. If not clearly, they may issue a warning or a style-warning. (E.g., omitting the value of the final branch of a cond or case expression when the value returned is stored in a place that is declared not to accept nil as a possible value, might be a style warning because the compiler cannot know that you do not know better.)
| While it is true that I don't have to restart my machine when I miscode in | Lisp, I still "crash and burn" - I incorrectly use a form - I pass arguments | in the wrong order, etc.
Then you have a _really_ bad Common Lisp environment.
| Little things that haunt every programmer - even Lispers.
No, not really. You see, a Common Lisp programmer who is uncertain about the arguments to a function he is about to call, asks the system for the lambda list. If he changes the lambda list in any way, he calls upon the system to let him edit all the calling points of that function. If he uses a smart editor, like Emacs, many of these operations can be fully automated, and this is again possible because (almost) all Common Lisp expressions are easy to scan both forwards and backwards in the editor, so you can move them around with simple editor commands.
| And, I assert, little things that *could* be detected by my language or | tool, instead of me, my debugger, or my users.
I assert that writing better code to begin with, working with a language that is actually small enough that it does not impose a cognitive load on its users. Common Lisp programmers make fewer syntactic bloopers because there is much less syntax, much better support for what syntax there is in their editors (although the other languages are getting some of the benefits of Lisp's superior syntax in Emacs). The tradeoff between how much the programmer, the editor, and the compiler does is very different between Java and Common Lisp.
If you think that whichever language you learned first has every implicit or tacit assumption right, you are wrong. The language you learned first was an accident. Learning a new langauge can be done two ways: Either drop everything you have gotten used to and open your mind to the new language, or study carefully how you react to things that differ and make those assumptions explicit. Anything else will fail. (Mixing works. :)
/// -- Norway is now run by a priest from the fundamentalist Christian People's Party, the fifth largest party representing one eighth of the electorate. -- Carrying a Swiss Army pocket knife in Oslo, Norway, is a criminal offense.
> The sooner I can discover a typo or an error, the better. I believe this is > true for any language.
I think the reason this is not as big an issue as you may feel is due to the interactive and incremental way development is best done in lisp. You do not need to write 6 screens full of code before it is ready to hand to the compiler to uncover all of your typos. I always test my functions as I write them and definately individually test them when they are "finished". The great thing about lisp development is that ability. You need to write method foo that will take an object of one of your classes and do something magical, create an object, bind it to something, even the same variable named in foo's lambda list and step by step make sure every line does what you expect. By the time that process is finished, the only errors left are the special cases you forgot and logical nuances. These are not the kind of things compilers will tell you about anyway.
In a certain sense this *is* discovering the typo and "d'oh" errors at runtime, but the (big) difference is that runtime is anytime.
"cp" <cperk...@medialab.com> writes: > I'm not necessarily advocating type specifications in Lisp.
They are already there in Common Lisp.
> The sooner I can discover a typo or an error, the better. I believe this is > true for any language.
Might I suggest the following then:
1. Learn to control the verbosity of your compiler and it's strictness. The compiler can tell you quite a bit of information before you hit "runtime." In cmucl, (setf *compile-verbose* t) will be helpful.
2. Use declaimations and/or declarations to set your compiler policy appropriatly. This will get quite a few typos. I put: (declaim (optimization (debug 3) (safety 3))) in my init file. Under cmucl I also: (setf *compile-print* nil) (declaim (optimize (inhibit-warning 3))) which reduced my console spam while compiling to the important warnings and notices.
> Lisp, I still "crash and burn" - I incorrectly use a form - I pass > arguments in the wrong order, etc. Little things that haunt every > programmer - even Lispers. And, I assert, little things that > *could* be detected by my language or tool, instead of me, my > debugger, or my users.
See above. What usually slips thru at this point may have possibly been caught by static type checking, but static type checking is not worth the trouble in CL, as Erik explained in another post.
> But, as has been suggested to me, I will read up on CLOS, I will check > Richard Waters' article on regression testing, and I'll muck about a bit.
CLOS will not help you much here, it's an object system, tho I still encourage you to learn about it. What will help you here is the Common Lisp Hyperspec and your CL implementations manual. Check out the sections on compiler policy and the use of type declarations in the CLHS, and your manuals section on the compiler and debugger.
> "Hmmmmm...that sounds like the type of problem Lisp would be well > suited to solve"
Yah, the solution is called macros and readtable 8)
-- Craig Brozefsky <cr...@red-bean.com> http://www.red-bean.com/~craig All around the world hearts pound with the rythym. Fear not of men because men must die. - Mos Def
In article <k5EK7.902$jt4.203...@news.uswest.net>, cp wrote: >> They also have a prohibitively expensive compilation process compared to >> Common Lisp. This is _why_ it is important in those languages to make >> sure that runtime is a less expensive debugging environment. >Comparisons >> of language tend to forget their contexts and evolution completely, and >> take one of these for granted while the other is treated like a stranger >> in a strange land. This is why you should strive to forget where you >> came from and try to figure out how your new language came to be and why >> what it does is natural in its environment.
>I'm not necessarily advocating type specifications in Lisp. I'm simply >trying to write better and more solid code, which is why I have come to Lisp >in the first place. I don't necessarily care to introduce type >specifications to Lisp, but I would be interested in upstream (not runtime) >verification of simple and complex contracts.
>The sooner I can discover a typo or an error, the better. I believe this is >true for any language.
I think that any benefits must be weighed against disadvantages. Is it wortwhile to save time finding a typo, if you have to pay for it in lost productivity at every turn? Is it wortwhile if it means that there are some programs you cannot write? Some programming languages force such a tradeoff on their users by requiring them to make declarations which allow checks to be easily implemented.
I think that static checking is perhaps a must only in safety critical software. Such software should be kept small and uncomplicated, because too much is at stake, so the lost productivity isn't an issue.
>> Coming from the C++ world, it can take a while to get used to programs >> that do not crash and burn.
>While it is true that I don't have to restart my machine when I miscode in >Lisp, I still "crash and burn" - I incorrectly use a form - I pass arguments >in the wrong order, etc.
But note that these situations are identified at a high level. Mistakes in a C++ program require a mapping from some machine language level back to the source language. Frequently, the mapping is difficult or impossible. A mistake that occured thousands of cycles earlier in the execution in an unrelated part of the program manifests itself in a mysterious failure elsewhere, perhaps in a subprogram that is itself correct.
You can't compare that to a high level diagnosis which tells you exactly what the problem is as it happens. (There can still be some level separation; e.g. a problem occurs deep in some code generated by a macro, without an easily apparent macro-level mapping back to the user of the macro).
C++ also doesn't let you programmatically choose the recovery, and certainly interactive recovery isn't part of a running C++ program; only when that program is running in a debugger.
Things like division by zero or dereferencing a null pointer are simply undefined behavior; they don't generate standard exceptions that can be caught. Programmers rely on platform-specific, nonportable mechanisms, like POSIX signals, or structured exception language extensions like those of Win32 or Digital UNIX or what have you.
> Little things that haunt every programmer - even >Lispers. And, I assert, little things that *could* be detected by my >language or tool, instead of me, my debugger, or my users.
Software shops waste tens of thousands of dollars on tools like Purify, yet crank out C++ that leaks and crashes. Money is wasted on the tools, time and money on their application.
>But, as has been suggested to me, I will read up on CLOS, I will check >Richard Waters' article on regression testing, and I'll muck about a bit.
>On an aside, I've always felt that Design Patterns, DBC, and UML are, at an >idealogical level, fundamentally tied together.
Many of the the design patterns described in the Design Patterns book are largely just C++ workaround patterns. It's shocking that the authors don't appear to realize that; if they do, they sure don't acknowledge it; their introduction claims that these are genuine patterns of object oriented design.
For example, the Adapter and Visitor patterns are meaningless in the context of an object system like CLOS, because you can create new generic functions with methods that specialize to existing classes, and because you have multiple dispatch. So from the perspetive of the CLOS user, these are, respectively, a recipe for overcoming the idiosynchrasies of a type system, and for clumsily simulating double dispatch.
Flyweights are just interning; happens all the time in Lisp when you use (interned) symbols.
Here is a good one: the State pattern. Basically, you have an empty object, which holds a pointer to another object. It redirects its calls to that object. It switches to other objects to represent state changes.
This is nothing more than a simulation of an object's *type* changing at run time! The ``context'' object appears to change type because its entire implementation is in the state object which is swapped for another one which has a different behavior.
In CLOS you can do that by directly by telling an object to change class. So you don't need a surrogate object to hold a pointer; you just have the state object and change it. Design Pattern, my ass! Say it with me: Workaround Recipe!
The State recipe is basically just reimplementing a virtual dispatch table in software, because the language doesn't give you the access to the real virtual table that would let you allow you to switch the virtual functions to make the object appear to change type.
For fun, I wrote a little C library in which a dynamic structure can, at run time, change its type among AVL, red-black, splay and ordinary binary trees as well as a sorted linear list. You can build a red-black tree, and then say ``please become an AVL tree''. Classes would only have gotten in the way of this, because I needed a way to switch the virtual table of a live object to a new set of methods.
Lastly, UML is basically a whole lot of graphical crud thrown together to try to capture every nuance of C++. It favors a narrow view of object orientation in which methods are properties of classes, exemplified by descendants of the Simula family. I think that the big names behind UML have a vested interest in selling you their design process at big bucks per seat. Just say no!
By the way, I pay my bills by developing in C++. Dirty, multithreaded, C++ with dynamic real-world inputs: communication protocol middleware. So I'm not some academic looking down on the trenches from an ivory tower. I use those workaround recipes, and from time to time I even have to produce or understand UM-Hell.
Hope you have as much fun reading this little rant as I had writing it. ;)
cp wrote: > I would be interested in upstream (not runtime) > verification of simple and complex contracts.
I just thought of another parallel, one going back twenty years.
It was a long, detailed, very serious argument I read in a tech journal against writing code which did not compile on the first shot.
I am sure no one understands what I just wrote, because it is so inconceivable. I repeat. The guy was saying that it was inexcusable to submit code to the compiler which would not compile successfully. His point was that it was lame to rely on the compiler to find stuff, one should desk check one's code thoroughly enough that one is sure the code will compile. Failures should leave one kicking oneself.
I do not recall why Mr CompileClean felt that way. Probably time wasted was one. back then 2500 lines of COBOL could take 30min to compile on PDP-11/70. But I recall also something more, that he thought programmers should be looking at what they wrote in your spirit of "the sooner the better".
I do not need to tell you that type checking upstream or down does not guarantee correct code. So if one is not looking at code closely enough to determine if the compiler will barf, clearly one is not looking closely enough to detect bugs. And I think that was a part of what MrCC was down on.
Which reminds me of another story from that same high visibility RSTS/E COBOL project. My manager saw me hunched over a listing and asked if there was some problem. I said no, I was just desk-checking the code. Relieved, he laughed and said we do not do that here. I was new. I said what do you do, just run it and see? He said, no, we move it into production, and started laughing.
> Hope you have as much fun reading this little rant as I had writing it. ;)
I liked your rant quite a bit. As a C++ programmer I "liked" the Design Patterns meaning that I understood the need for them, but I always felt they were at heart just a kludge. Really, just a kludge on top of a kludge. This is part of the reason I am learning Lisp now.
> I do not follow you. [...deleted...] Or do you mean that you want
static type checking? That you cannot get
> very easily.
Sorry about that, the message went out before I got to re-read and edit it. Static type checking would have been most analagous. However, I am in no means advocating static type checking. I would not cut even a single hair off Lisps dynamic language head.
I don't want to limit what can or can't be done in the Lisp. I don't want the compiler to throw errors. I only want something that will help me identify errors without having to discover them at runtime. Maybe something like (check-function-for-obvious-errors 'function) Think less about type errors and more about the situations you use assert clauses for right now.
> I disagree, probably to your surprise. The earlier an error is detected, > the _firmer_ the determination that it is an error is, and the simpler > the world must be in which statements are made.
True, but our code does make simple statements from time to time. And it would be nice if the simpler bugs could be vetted out. And it would be even nicer if not so simple bugs could be discovered.....
> I assert that writing better code to begin with, working with a language > that is actually small enough that it does not impose a cognitive load on > its users.
Yes, this is the most salient point against adding artificial constructs (like DBC) to shore up our code. The more code, the harder it is to keep all the details in ones head, and coding is a detail oriented job. So, as programmers we try to fight this with elegance and simplicity, and when those aren't enough we modularize.
When working on some recent large Java/C++ projects I used DBC extensively and to great effect. Postconditions, preconditions, invariants, etc. were all keeping the code ship shape. It was especially useful when the following year the product was developed into its 2.0 version. There were a lot of conditional checks, and many were fairly trivial. But every now and then we'd make a change to something and despite great care to handle all the consequences we'd set off a DBC assertion in testing. And, hey, that's great - without that DBC check that little error might have gone undetected or propogated itself into other problems. But, often once the problem was examined I would realize that it was one of the trivial assertions that caught it, and I'd think to myself "That assertion clause wasn't complicated - I could write something to vette that out systematically". But, of course, in Java or (worse) C++ one really couldn't easily write something like that. But, in Lisp, well that's a different story...
Anyway, I know Java and C++ are terribly brittle, and maybe as I work more in Lisp I'll just perceive DBC, assert clauses, and the like as artificial kludges for addressing the shortcomings of static languages. Maybe.... or maybe not. I still use assert clauses in my Lisp code (though, thankfully, not nearly as many) and a few seem like they could be systematically verified.
Chris Perkins Media Lab, Inc. / As Is Software, Inc.
> Many of the the design patterns described in the Design Patterns book > are largely just C++ workaround patterns. It's shocking that the authors > don't appear to realize that; if they do, they sure don't acknowledge it; > their introduction claims that these are genuine patterns of object > oriented design.
I can't agree with you more; heck, I've tried to convince my co-workers of it for years, BUT, to be fair, at least _one_ of the authors _does_ realize it (I think it's Ralph Johnson, he's actually a smalltalker). One of the patterns at least says that it's not useful in smalltalk. [Book's at the office, so I can't check up on which right now]. But I figure the other 3 authors (and the publishers) made sure this point didn't come across too clearly...
As you say, next thing you know, people would stop buying Rose and Purify and actually use programming environments that work... shudder. :-)
The amazing thing is, back when I was a C++ programmer, I thought that was SUCH a great book. And I guess it was, in a way: it made it possible to use C++ and get something done. In the long run, of course, that may have been a disservice...
-- It would be difficult to construe Larry Wall, in article this as a feature. <1995May29.062427.3...@netlabs.com>
jlo...@hushmail.com writes: > On Wed, 21 Nov 2001 02:54:06 GMT, Kenny Tilton <ktil...@nyc.rr.com> wrote:
> >Sometimes I cannot because I use Semaphors a lot and these are > >structures that arrange for a slot to be efficiently envalued by a > >formula--if I die in a formula, fixing the formula in the source does > >not change the lambda function that died, so I just "burn" the window > >and re-run. without linking.
> What are these Semaphors? I guess it's not the IPC thing you're talking about, > no? :)
No, it's his wacky[*] terminology for "constrained slot" :-)
[*] I really don't see the logic in the name. "semaphore" litterally means "sign bearer". Or it can be the railroad thing, or the flag thing. Maybe it's a physical metaphor for the railroad semaphore? Or maybe there's another meaning of semaphore I don't know about? Actually, I called it wacky, hoping that Kenny will explain the name :-)
-- /|_ .-----------------------. ,' .\ / | No to Imperialist war | ,--' _,' | Wage class war! | / / `-----------------------' ( -. | | ) | (`-. '--.) `. )----'
> > On Wed, 21 Nov 2001 02:54:06 GMT, Kenny Tilton <ktil...@nyc.rr.com> wrote:
> > >Sometimes I cannot because I use Semaphors a lot and these are > > >structures that arrange for a slot to be efficiently envalued by a > > >formula--
> > What are these Semaphors? I guess it's not the IPC thing you're talking about, > > no? :)
No, but see below.
> No, it's his wacky[*] terminology for "constrained slot" :-)
> [*] I really don't see the logic in the name. "semaphore" litterally > means "sign bearer".
Right, the "bearer" part conveys the dataflow, and the "sema" says "meaning" to me, so I get "meaning bearer", tho "sign bearer" is what the dictionaries say.
While Semaphor may be wacky, "constrained" is wrong. A constraint to me /partially/ specifies a value in terms of other values. So I might have (< x y) as a constraint on the values x can assume. The constraint does not determine the value of x, it keeps x from becoming GE y at runtime.
Semaphors are CLOS slots that act like cells in a spreadsheet. A formula /fully/ determines the value for the slot. If that is a constraint it is a constraint with extreme prejudice.
Our first implementation used a lambda whose sole argument was the instance owning the slot, but this had two problems. One, it was slow. Two, where a slot value effected the screen appearance, lazy evaluation is no good; a change to the value requires a screen update. Garneters must manually call update-window or somesuch. Similarly, if x depends on y and y changes, maybe y does not effect the screen but maybe x does and x would return a new value if recalculated. So we arranged for eager evaluation, with lots of optimizations to make that efficient.
[on IPC: we used win32 COPYDATA to splice semaphoric dataflow between processes.]
The eager evaluation then means that a change to a single semaphoric slot causes a cascade of dependent slot changes and (via a GF callback specialized on the instance, new value and old value) to any necessary manifestation outside the semaphoric web, such as a screen redraw.
I felt the metaphor of "bearing" was apt for the process of propagating state. And in my metaphysics the meaning of anything is the sum of its causes. Eager evaluation is cause-and-effect. Semantics is about meaning. meaning+propagation = semantics+bearer = Semaphor. Misspelled because I dig the word "metaphor" and so I can trademark it. :)
> I disagree, probably to your surprise. The earlier an error is detected, > the _firmer_ the determination that it is an error is, and the simpler > the world must be in which statements are made.
* Chris Perkins | True, but our code does make simple statements from time to time.
I was referring to the statement "this is an error".
| And it would be nice if the simpler bugs could be vetted out.
As you grow more accustomed to programming in a better language, you will notice that you make far fewer "simple bugs". I am serious about this.
| Anyway, I know Java and C++ are terribly brittle, and maybe as I work | more in Lisp I'll just perceive DBC, assert clauses, and the like as | artificial kludges for addressing the shortcomings of static languages.
The overspecificity of these languages produces the need for tools to combat their cognitive load. Having to specify so much detail so early it just plain wrong, so you need tools that can help you keep things together when you have to make changes as you learn what you should have known by the time you specified these things. The nature of the problem you are solving becomes known to you only as you try to solve it.
I believe C++ instills fear in programmers, fear that the interaction of some details causes unpredictable results. Its unmanageable complexity has spawned more fear-preventing tools than any other language, but the solution _should_ have been to create and use a language that does not overload the whole goddamn human brain with irrelevant details. Striking that balance between language complexity and the convenience of using it is amazingly hard, but I think Common Lisp is closer to this lofty ideal than any other I have used. That the syntax is so predictable and so easy to navigate using the proper editor, causes many more benefits than people realize early on. I think the best advice right now is that you should relax all those "needs" you have because you come from a C++/Java environment.
/// -- Norway is now run by a priest from the fundamentalist Christian People's Party, the fifth largest party representing one eighth of the electorate. -- Carrying a Swiss Army pocket knife in Oslo, Norway, is a criminal offense.
> Seems interesting, but I'm not sure if I follow you. Could you post a concrete > example of its use and usefulness? O:)
Well, if you know Garnet you need read no further, Semaphors are like the constraints/demons tho with important differences such as eager vs. lazy evaluation.
If you do not know Garnet, here is a simple example (with important details missing):
"self" I throw in out of nowhere ala Smalltalk and C++ this. It is the instance that owns the slot.
(selector self) returns (trust me) some cooperating supervisory instance which keeps track of the selection defined by several buttons.
the ^macros arrange for behind-the-scenes dependency maintenance in support of eager re-evaluation. now that I grok specials the ^macros are superfluous, but I am still implementing that in an experimental branch of development.
when the controlAction gets invoked (by a mechanism not shown) the selection semaphor of the selector gets a new value. the dataflow engine (DF) then causes the selected state to go non-nil. then DF makes the forecolor of the instance in question change from blue to red. the echo function associated with forecolor then triggers the screen redraw.
---------------------------------
usefulness? the grail. the silver bullet. better programmer productivity:
- eliminates an entire class bug, viz, internal inconsistency arising from oversights in state propagation. and it eliminates all the coding required to propagate state throughout the application model.
- more self-documenting since it gathers the semantics of any slot in one rule
- allows OO to deliver on the promise of re-use, since instances can be assigned their own rules (even closing over different lexically-bound values), in turn allowing us to get more varied behavior out of fewer classes than is possible when slot parameters cannot be functional or can be functional but must have the same rule for each instance of a slot.
- eliminates the exponential growth of complexity as applications increase in functionality. example from another domain: if one models in detail the cashflow of a small business with a spreadsheet one ends up with a quite complex model in which no cell formula ever taps more than a few values. the spreadsheet author only has to worry about one formula at a time, not the whole system all at once. the complexity emerges from so many simple formulas. same with semaphors.
- it's great fun (as I think Garnetites can confirm)
Kenny Tilton <ktil...@nyc.rr.com> writes: > I do not recall why Mr CompileClean felt that way. Probably time wasted > was one. back then 2500 lines of COBOL could take 30min to compile on > PDP-11/70. But I recall also something more, that he thought programmers > should be looking at what they wrote in your spirit of "the sooner the > better".
At that time in mainframe shops, you handed in your punch cards, they were run and your listing was returned to you and your next shot at the machine could be in another four hours. Don't assume an interactive environment that is continuously available. In that context desk checking was very valuable.
-- Lieven Marchand <m...@wyrd.be> She says, "Honey, you're a Bastard of great proportion." He says, "Darling, I plead guilty to that sin." Cowboy Junkies -- A few simple words
Erik Naggum <e...@naggum.net> writes: > I disagree, probably to your surprise. The earlier an error is detected, > the _firmer_ the determination that it is an error is, and the simpler > the world must be in which statements are made. For instance, we have > the option in Common Lisp of redefining a function between the definition > the compiler knew about at compile-time of a caller and the actual time > it was called. This would require a total re-compile in languages that > consider such things an error with no option to negotiate. In the long > history of programming languages, indeed human history, there are people > who insist that they know what is true and good, and who seek to limit > other people's opportunity to find out what other things are true and > good. These generally call anything they disagree with "errors" and make > people angry at their compilers or computers. Then there are people who > only think they know what is wrong, and while they have their own ideas > about what is true and good, they only seek to limit other people's > opportunity to do wrong and harmful things. These generally do not think > things are errors unless they are _clearly_ wrong and harmful. If not > clearly, they may issue a warning or a style-warning. (E.g., omitting > the value of the final branch of a cond or case expression when the value > returned is stored in a place that is declared not to accept nil as a > possible value, might be a style warning because the compiler cannot know > that you do not know better.)
I've always liked the Poplog terminology. They call errors mishaps and have the same philosophy as CL about them. Off course, poplog is also a highly dynamic language that grew up in AI research, so the similarity is not surprising.
-- Lieven Marchand <m...@wyrd.be> She says, "Honey, you're a Bastard of great proportion." He says, "Darling, I plead guilty to that sin." Cowboy Junkies -- A few simple words
Kenny Tilton <ktil...@nyc.rr.com> writes: > While Semaphor may be wacky, "constrained" is wrong. A constraint to me > /partially/ specifies a value in terms of other values. So I might have > (< x y) as a constraint on the values x can assume. The constraint does > not determine the value of x, it keeps x from becoming GE y at runtime.
(with-disclaiming I really know jack about constraints, so I usually just refer to KR, and KR slots. Which avoids any discussion about the nature of those slots (so long as people know what KR is -- but if they don't, I hand them the manual).)
[...]
> I felt the metaphor of "bearing" was apt for the process of propagating > state. And in my metaphysics the meaning of anything is the sum of its > causes. Eager evaluation is cause-and-effect. Semantics is about > meaning. meaning+propagation = semantics+bearer = Semaphor. Misspelled > because I dig the word "metaphor" and so I can trademark it. :)
Okay, this makes sense to me now. The "sema" in semaphore *really* means sign to me because of, y'know, that whole thing where semaphores are mechanical things that have actual signs. Oops, I didn't notice you were dropping the -e -- that's a great excuse to redefine the meaning of a word, you re-derived it from the greek (and according to English conventions, even, not French).
-- /|_ .-----------------------. ,' .\ / | No to Imperialist war | ,--' _,' | Wage class war! | / / `-----------------------' ( -. | | ) | (`-. '--.) `. )----'
> Okay, this makes sense to me now. The "sema" in semaphore *really* > means sign to me because of, y'know, that whole thing where semaphores > are mechanical things that have actual signs.
right. when i was analyzing the name i was reminded also that that was (is?) how ships communicated when near each other, with someone waving about paddles (semaphores).
> Oops, I didn't notice > you were dropping the -e -- that's a great excuse to redefine the > meaning of a word,
<heh-heh> this is marketing, we don't need an excuse to redefine words.
constraints make me think of handcuffs, leg irons, strait jackets, duct tape... kinky, but not my thing, and entirely the wrong connotation for an empowering productivity hack.