> Does anyone have any good examples of using them?
Allegro's regular expression package has defined a compiler macro for MATCH-REGEXP that will call COMPILE-REGEXP when the regular expression is a constant string.
-- Lieven Marchand <m...@bewoner.dma.be> If there are aliens, they play Go. -- Lasker
> Does anyone have any good examples of using [compiler macros]?
In a commercial project I am working on, we use compiler macros in a few places. Here are some examples (variable names have been changed to protect the innocent):
(defun calculate-foo (bar baz) (+ bar (* baz +foo-constant+)))
(define-compiler-macro calculate-foo (&whole form bar baz &environment env) (if (and (constantp bar env) (constantp baz env)) (calculate-foo bar baz) form))
In this case, we noticed that the function calculate-foo was frequently, but not always, used where the arguments were available at compile time. The compiler macro checks to see if the arguments are constant, and if so, computes the value at compile time and uses that. If not, the form is left unchanged.
This one is hairier:
(defun mapfoo (function element-generator) (loop while (element-available? element-generator) do (funcall function (get-next-element element-generator))))
In this case, we often found that MAPFOO was called with a literal lambda expression like this:
(mapfoo #'(lambda (a-foo) (frob a-foo t nil)) (get-foo-generator))
The compiler-macro looks for this pattern, and if it finds it, turns it into
(let ((#:G1234 (get-foo-generator))) (loop while (element-available? #:G1234) do (let ((a-foo (get-next-element #:G1234))) (frob a-foo t nil))))
So we can use the higher-order function mapfoo everywhere, but still get the performance of the obvious loop function when we know what the mapped function will be. This avoids the consing of a closure, the multiple levels of function calls, and the indirect variable lookup.
> > compiler macros provide the best of both worlds, and can be quite the > > tool to optimize code beyond belief without being force into macro land.
Compiler macros are a useful tool. In this case I can write my code in a more abstract style and not think about optimization. Then, when I discover some of the bottlenecks, I can add a compiler macro to optimize the critical sections without doing a single rewrite of the existing code.
On the other hand, compiler-macros are macros, and they come with all the variable capture problems of regular macros. They have one great advantage of allowing you to punt and return the original form if things get too hairy.
But in both cases I have outlined above, the compiler-macros are acting as a `poor-man's inliner'. If inline declarations worked, I could have simply declared the relevant functions as inline and not bothered with the compiler-macros at all. Most of our uses of compiler-macros are to make up for this deficiency.
There are uses for compiler-macros other than getting around the lack of inlining: Suppose you are writing a function that has some constraints on its arguments, e.g., the first argument should be a compile-time constant number. You can write a compiler macro to check that this is the case.
Lieven Marchand <m...@bewoner.dma.be> writes: > cba...@2xtreme.net (Christopher R. Barry) writes:
> > Does anyone have any good examples of using them?
> Allegro's regular expression package has defined a compiler macro for > MATCH-REGEXP that will call COMPILE-REGEXP when the regular expression > is a constant string.
regexp.cl isn't included with a trial edition but I guess I get the idea.
On Sat, 11 Mar 2000 02:57:19 GMT, cba...@2xtreme.net (Christopher R. Barry) wrote:
> Does anyone have any good examples of using them? None of the Lisp > books out there give them any non-reference coverage at all. The > HyperSpec shows some short examples, but I'm curious how real > programmers use them in real programs.
The following recent paper deals with compiler macros:
"Increasing Readability and Efficiency in Common Lisp" Ant\'{o}nio Menenzes Leit\~{a}o aml AT gia DOT ist DOT utl DOT pt Proceedings of the European Lisp User Group Meeting '99
Abstract: Common Lisp allows programmer intervention in the compilation process by means of macros and, more appropriately, compiler macros. A compiler macro describes code transformations to be done during the compilation process. Unfortunately, compiler macros are difficult to write, read and extend. This article presents a portable extension to Common Lisp's compiler macros. The extension allows easy definition and overloading of compiler macros and is based in two known techniques, namely pattern matching and data driven programming. Three different uses are presented:
(1) Code optimization, (2) Code deprecation, and (3) Code quality assessment. The extension is being used extensively in the reengineering of a large AI system, with good results.
As I write this I am offline and I don't know whether the paper is also available at a Web site. If you need a copy of the proceedings you may contact Franz's sales department. The proceedings include another paper by the same author, titled "FoOBaR - A Prehistoric Survivor", which illustrates the evolution of the large AI system mentioned in the abstract.
Andrew Cooke wrote: > Because I program in Java and a (the one> ;-) advantage over Lisp is > that it catches some mistakes that dynamic testing doesn't. I know that > one can have tests etc, but it seems that the earlier one catches errors > the better. The original post was about software which is effectively > run once (outside testing) and must work - I was thinking that the > extreme emphasis on perfect code must favour anything that detects > errors.
Having programmed in Scheme as well as in a bunch of statically typed languages (including C, C++, Ada and Java), in my experience it actually doesn't matter.
I find that something which will show up at compile time in an ST language will show up immediately on even the most cursory of testing in a DT language. Call a function with the wrong type and it'll usually give an error the first time you run the code (whereas calling with incorrect values may require extensive testing to find). The bugs that make it past testing into production code, IME, are of a very different nature, and usually result from misunderstood specifications or poor design.
There is an advantage in *convenience*: in an ST language I can have a bunch of errors found for me for the effort of typing 'make' rather than having to run tests. OTOH, there's a cost in convenience of having to write the type declarations (and of having to explicitly say so when you really do want 'any type', e.g. for collection classes). On the third hand, I usually need to figure out what the types should be anyway from a design point of view. Six of one, half a dozen of the other.
I'm inclined to the opinion that this whole issue of typing errors is worth, at a generous estimate, a tenth the amount of ink/electrons that's been spilled over it :)
-- "To summarize the summary of the summary: people are a problem." Russell Wallace mailto:manor...@iol.ie
Russell Wallace <manor...@iol.ie> writes: > Andrew Cooke wrote: > > Because I program in Java and a (the one> ;-) advantage over Lisp is > > that it catches some mistakes that dynamic testing doesn't. I know that > > one can have tests etc, but it seems that the earlier one catches errors > > the better. The original post was about software which is effectively > > run once (outside testing) and must work - I was thinking that the > > extreme emphasis on perfect code must favour anything that detects > > errors.
> Having programmed in Scheme as well as in a bunch of statically typed > languages (including C, C++, Ada and Java), in my experience it actually > doesn't matter.
Ah, but have you programmed in a language that has a *proper* types system, like Haskell or ml? For *certain classes of problem* I find the thinking I have to do to work out the types of things leads to insights into the problem I probably wouldn't otherwise have had.
I had a minor epiphany in this direction just the other day, so I hope you'll forgive the evangelism.
Cheers, Michael
-- very few people approach me in real life and insist on proving they are drooling idiots. -- Erik Naggum, comp.lang.lisp
manor...@iol.ie wrote: > There is an advantage in *convenience*: in an ST language I can have a > bunch of errors found for me for the effort of typing 'make' rather than > having to run tests. OTOH, there's a cost in convenience of having to > write the type declarations (and of having to explicitly say so when you > really do want 'any type', e.g. for collection classes). On the third > hand, I usually need to figure out what the types should be anyway from > a design point of view. Six of one, half a dozen of the other.
It's not just running tests, but also writing them and making sure they check all the things that a compiler would check for a static-typed language - you would have tests anyway, but you might well need more without static types.
(But I agree with some of what you've said and other posts on this thread show that some static type checking is possible - it varies significantly with the implementation).
> * Jon S Anthony wrote: > > still do. This stuff _is_ like the "physical world context" as it > > _does_ have constraints on the total package size. For example, you > > typically can't use some new wonder processor with a 1/2 Gig of RAM, > > but rather some rather primitive thing (perhaps a decade or so old) > > and with very limited amounts of memory and such. Why use this > > "junk"? Because they are _hardened_ in various ways that the newer > > stuff isn't. Constraints may literally be the amount of generated > > code as it won't fit on the available ROM.
> I have never heard a claim that the Ariane code was up against memory > size limits. Do you have any evidence for that?
No one here has made that claim and nothing in the above paragraph says otherwise.
> Incidentally, please don't assume I'm not aware of the issues with
I have not made that assumption.
> > But that's not even the point. When you design and build something > > for a given context and you clearly specify what this context of use > > is (whether you think such design and construction is "good" or not is > > completely _irrelevant_ to the point), then if someone _ignores_ this > > approved context of use, the burden is _not_ on the builder of the > > component, but rather on the user of it. That's the point.
> Right. BUT THAT IS NOT WHAT I WAS ARGUING ABOUT. I was arguing > about the specific claim made in the second paragraph of section 2.2 > of the report on the disaster. And that is *all*.
Then we have simply been talking past one another. After all, the message you replied to by me was _only_ about this point and that is all. Go back and look. Which is why I kept remarking that you were missing the point. If you didn't want to discuss this point, then there was no point in replying to that message in the first place. Criminey!
> Sigh.
Really.
/Jon
-- Jon Anthony Synquiry Technologies, Ltd. Belmont, MA 02478, 617.484.3383 "Nightmares - Ha! The way my life's been going lately, Who'd notice?" -- Londo Mollari
cbbro...@knuth.brownes.org (Christopher Browne) writes: > Static typing may provide *some* of the testing implicitly; the real > point is that type errors are only a subset of the total list of possible > errors that may be made in writing a program.
The errors its tends to find are the common ones that are completely avoidable (admittedly much more prevalent in the C family of languages than than in Lisp).
> It's not that "static typing is downright bad, and never finds errors > for you."
> It is rather the case that if you construct the "unit tests" *that should > be built* to examine boundary cases and the likes, this will indirectly > provide much of the testing of types that you suggest need to be implicit > in the type system.
Yes an no. Unit testing is fundamentally important even with static typing. A good test would indeed have boundary cases to catch the typing errors. But now you have to do the tedious effort of writing those cases. It is better if those cases can't even be expressed in the first place. For example, how many kinds of invalid values can you pass to a function? A string, an atom, a list, a vector, a float, an integer,... If your function was restricted to integers, say, then you could simply concentrate on the invalid integer values.
> If there are good unit tests, this diminishes the importance of static > type checking. Not to zero, but possibly to the point of not being > dramatically useful.
Static typing is dramatically useful in integration tests. A unit test will not exhaustively exercise other units. Static typing allows units to fit together without the piles of dumb stupid interface errors, allowing you to concentrate on the smart stupid logic errors :-).
-- Cheers, The Rhythm is around me, The Rhythm has control. Ray Blaak The Rhythm is inside me, bl...@infomatch.com The Rhythm has my soul.
>In article <38CCF7CF.7...@iol.ie>, > manor...@iol.ie wrote: >> There is an advantage in *convenience*: in an ST language I can have a >> bunch of errors found for me for the effort of typing 'make' rather >than >> having to run tests. OTOH, there's a cost in convenience of having to >> write the type declarations (and of having to explicitly say so when >you >> really do want 'any type', e.g. for collection classes). On the third >> hand, I usually need to figure out what the types should be anyway >from >> a design point of view. Six of one, half a dozen of the other.
>It's not just running tests, but also writing them and making sure they >check all the things that a compiler would check for a static-typed >language - you would have tests anyway, but you might well need more >without static types.
Static typing may provide *some* of the testing implicitly; the real point is that type errors are only a subset of the total list of possible errors that may be made in writing a program.
It's not that "static typing is downright bad, and never finds errors for you."
It is rather the case that if you construct the "unit tests" *that should be built* to examine boundary cases and the likes, this will indirectly provide much of the testing of types that you suggest need to be implicit in the type system.
If there are good unit tests, this diminishes the importance of static type checking. Not to zero, but possibly to the point of not being dramatically useful. -- "Bother," said Pooh, as he deleted his root directory. cbbro...@hex.net - - <http://www.ntlug.org/~cbbrowne/lsf.html>
* Ray Blaak <bl...@infomatch.com> | For example, how many kinds of invalid values can you pass to a function? | A string, an atom, a list, a vector, a float, an integer,... If your | function was restricted to integers, say, then you could simply | concentrate on the invalid integer values.
(check-type <argument> <restricted-type>) takes care of this one for you, or you can use assert or throw your own errors if you really have to. I don't see the problem. writing safe code isn't hard in Common Lisp.
| Static typing is dramatically useful in integration tests. A unit test | will not exhaustively exercise other units. Static typing allows units | to fit together without the piles of dumb stupid interface errors, | allowing you to concentrate on the smart stupid logic errors :-).
when the compiler stores away the type information, this may be true. when the programmers have to keep them in sync manually, it is false.
static typing is like a religion: it has no value outside the community of believers. inside the community of believers, however, it is quite impossible to envision a world where static types do not exist, and they think in terms that restrict their concept of "type" to that which fits the static typing religion.
to break out of the static typing faith, you have to realize that there is nothing conceptually different between an object of type t that holds a value you either know or don't know how to deal with, and an object of a very narrow type that holds a value you either know or don't know how to deal with. the issue is really programming pragmatics. static typing buys you exactly nothing over dynamic typing when push comes to shove. yes, it does buy you something _superficially_, but look beneath it, and you find that _nothing_ has actually been gained. the bugs you found are fixed, and the ones you didn't find aren't fixed. the mistakes you made that escaped the testing may differ very slightly in expression, but they are still there. the mistakes you did find may also differ slightly in expression, but they are still gone. what did you gain by believing in static typing? pain and suffering and a grumpy compiler. what did you gain by rejecting this belief and understanding that neither humans nor the real world fits the static typing model? freedom of expression! of course, it comes with a responsibility, but so did the static typing, only the dynamic typing responsibility is not to abuse freedom, while the static typing responsibility is not to abuse the power of restriction.
personally, I think this is _actually_ a personality issue. either you want to impose control on your environment and believe in static typing, or you want to understand your environment and embrace whatever it is.
It's not a specific response to Erik's post just some info on the subject.
For the static typing believers (of which i'm not) here is a new language that is supposed to be a "Typed Lisp" "even faster than C" "As a summary: Pliant is typed-Lisp + C++ + reflective-C in one language"
They got rid of the GC, which is rather unusal for a lisp...
Erik Naggum <e...@naggum.no> writes: > * Ray Blaak <bl...@infomatch.com> > | Static typing allows units > | to fit together without the piles of dumb stupid interface errors, > | allowing you to concentrate on the smart stupid logic errors :-).
> when the compiler stores away the type information, this may be true. > when the programmers have to keep them in sync manually, it is false.
How so? If there is an error, the programmer has to manually fix things anyway, regardless of whether or not it was automatically discovered, or whether or not the compiler stores type information. Maybe I misunderstood what you mean by "keep them in sync".
> static typing is like a religion [...]
The point is that having tool support to detect the kinds of errors that static typing can discover is helpful. In my experience these errors are very common, easy to detect, and easy to fix. It is only a useful tool, no fanatical zeal required.
I freely admit, however, that with Lisp systems at least, the consequences of no static typing are not as important as with other languages, mainly due to the built-in abilities of the language. One just doesn't corrupt things as easily.
> the issue is really programming pragmatics. static typing > buys you exactly nothing over dynamic typing when push comes to shove. > yes, it does buy you something _superficially_, but look beneath it, and > you find that _nothing_ has actually been gained. the bugs you found are > fixed, and the ones you didn't find aren't fixed. the mistakes you made > that escaped the testing may differ very slightly in expression, but they > are still there. the mistakes you did find may also differ slightly in > expression, but they are still gone.
I disagree with the "slightly". Pragmatically speaking, I have found the number of errors prevented to be significant. One still has real bugs of course, it's just the the silly ones I don't have to worry about.
> what did you gain by believing in static typing? pain and suffering and a > grumpy compiler. what did you gain by rejecting this belief and > understanding that neither humans nor the real world fits the static > typing model? freedom of expression!
I encounter this attitude of "fighting the compiler" often, and I don't understand it. A programmer describes an abstraction to a computer. The computer then adheres to it, and points out violations that do not conform to it. If one finds that they are bumping against a restriction that means either the abstraction is not general enough, or that there is an error of usage that should be fixed. The programmer has complete freedom to change things. One does not fight the compiler per se; rather, one discovers the correctness of their design.
It's not about "believing" in static typing. It's about being able to describe an abstraction to an appropriate level of detail. Any tool support that can aid in verifying it is only a win.
> of course, it comes with a responsibility, but so did the static typing, > only the dynamic typing responsibility is not to abuse freedom, while the > static typing responsibility is not to abuse the power of restriction.
I don't agree with this dichotomy. To me there is only freedom. Dynamic vs static typing is actually a false distinction. They are really different points along a continuum of "typing". One makes abstractions as general or as constrained as necessary, balancing flexibility, robustness and correctness.
The responsibility is simply to do things "right" :-).
> personally, I think this is _actually_ a personality issue. either you > want to impose control on your environment and believe in static typing, > or you want to understand your environment and embrace whatever it is.
I think that one can and should do both. The art is in acheiving the appropriate balance.
-- Cheers, The Rhythm is around me, The Rhythm has control. Ray Blaak The Rhythm is inside me, bl...@infomatch.com The Rhythm has my soul.
>cbbro...@knuth.brownes.org (Christopher Browne) writes: >> Static typing may provide *some* of the testing implicitly; the real >> point is that type errors are only a subset of the total list of possible >> errors that may be made in writing a program.
>The errors its tends to find are the common ones that are completely avoidable >(admittedly much more prevalent in the C family of languages than than in >Lisp).
>> It's not that "static typing is downright bad, and never finds errors >> for you."
>> It is rather the case that if you construct the "unit tests" *that should >> be built* to examine boundary cases and the likes, this will indirectly >> provide much of the testing of types that you suggest need to be implicit >> in the type system.
>Yes an no. Unit testing is fundamentally important even with static typing. A >good test would indeed have boundary cases to catch the typing errors. But now >you have to do the tedious effort of writing those cases. It is better if those >cases can't even be expressed in the first place. For example, how many kinds >of invalid values can you pass to a function? A string, an atom, a list, a >vector, a float, an integer,... If your function was restricted to integers, >say, then you could simply concentrate on the invalid integer values.
This assumes that the function is necessarily supposed to be restricted to integers.
The "Lisp assumption" is that this is *not* the case.
Alternatively, if this *should* be the case, then it may make sense to define the function as a CLOS method, and attach typing information to the method, at which point you get similar guarantees to those provided by static type checking.
But at any rate, by the time you've thrown boundary case testing at the code, it is relatively unimportant what kinds of values are invalid.
If each function has been tested to verify that it accepts reasonable input, and produces reasonable output, I'm not terribly worried about how it copes with unreasonable input/output. After all, *all* of the functions are producing reasonable output, right?
>> If there are good unit tests, this diminishes the importance of static >> type checking. Not to zero, but possibly to the point of not being >> dramatically useful.
>Static typing is dramatically useful in integration tests. A unit >test will not exhaustively exercise other units. Static typing allows >units to fit together without the piles of dumb stupid interface >errors, allowing you to concentrate on the smart stupid logic errors > :-).
I have yet to write CLOS code, but it looks to me like that (or "tiny-CLOS" schemes), combined with packaging, are the ways that Lisp-like systems can attack the "interface control" problem...
The point here is that CLOS does "type-based" dispatching which grapples with at least part of the "dumb stupid" interface problems.
And the other point that you appear quite obstinate in refusing to recognize is that as soon as one pushes even *faintly* realistic test data through a set of functions, this *quickly* shakes out much of those "piles of dumb stupid interface errors." -- Q: If toast always lands butter-side down, and cats always land on their feet, what happens if you strap toast on the back of a cat and drop it? A: it spins, suspended horizontally, a few inches from the ground. cbbro...@ntlug.org- <http://www.ntlug.org/~cbbrowne/lsf.html>
Ray Blaak wrote: > The point is that having tool support to detect the kinds of errors that > static typing can discover is helpful. In my experience these errors are very > common, easy to detect, and easy to fix. It is only a useful tool, no > fanatical zeal required.
Hmm. My experience is that most of the type errors I make when programming in C or C++[1] are ones that don't really have counterparts in Lisp. I mistype a declaration, or accidentally refer to |foo| where I mean |*foo| or vice versa, or use a signed integral type when I want an unsigned one; in Lisp I don't write declarations until later, when I'm thinking hard about types of objects, andconfusing an object with its address is an issue that just doesn't arise, and integers are *real* integers :-).
What sort of type errors do you make that get caught in languages with static typing?
> I encounter this attitude of "fighting the compiler" often, and I > don't understand it. A programmer describes an abstraction to a > computer. The computer then adheres to it, and points out violations > that do not conform to it. If one finds that they are bumping > against a restriction that means either the abstraction is not > general enough, or that there is an error of usage that should be > fixed. The programmer has complete freedom to change things. One > does not fight the compiler per se; rather, one discovers the > correctness of their design.
If all languages were elegantly and consistently designed, that might be true. But when there are gratuitous restrictions or inconsistencies or complications in the language design, it seems perfectly reasonable to me to speak of "fighting the compiler" (or, maybe, "fighting the language").
Having to write
#define FOO(x) do { ... } while (0)
in C is "fighting the compiler". So is having to make up names for intermediate objects if you want to set up (at compile time) a nested structure where the nesting is done via pointers. So is having to allocate and free temporary objects explicitly on account of there being no GC.
All these things are just consequences of the language being how it is, yes. That doesn't mean that the restrictions and annoyances aren't real. The programmer *doesn't* have complete freedom to change things; s/he can't change the language. I don't think that "it's unpleasant to express in C" indicates a flaw in the design of a program. It might indicate a flaw in the design of C.
I'm not saying that a design should take no notice of the language it's likely to get implemented in, of course. I'm saying that often the best design still lumbers you with some annoyances in implementation that could have been alleviated if the language had been slightly different, and that these count as "fighting the compiler".
[1] I haven't done anything non-trivial in languages like ML and Haskell that are statically typed but have "better" type systems than C's.
-- Gareth McCaughan Gareth.McCaug...@pobox.com sig under construction
cbbro...@news.hex.net (Christopher Browne) writes: > Centuries ago, Nostradamus foresaw a time when Ray Blaak would say: > If each function has been tested to verify that it accepts reasonable > input, and produces reasonable output, I'm not terribly worried about > how it copes with unreasonable input/output. After all, *all* of the > functions are producing reasonable output, right?
Testing with bad inputs is a fundamental part of testing. This is definitely a religious belief on my part. If you don't agree, we'll simply leave it at that.
> And the other point that you appear quite obstinate in refusing to > recognize is that as soon as one pushes even *faintly* realistic test > data through a set of functions, this *quickly* shakes out much of > those "piles of dumb stupid interface errors."
Actually I agree with this. My point was to be able to avoid doing the work of writing certain kinds of (tedious) tests in the first place. That is, let the computer effectively do it for me.
Also, consider that unit tests often use "stub" versions of other units, since they might not be available yet, be mutually dependent, etc. In that case exercising your unit can test it adequately, but you can still miss bad calls to the other units due to differences in behaviour between the stubbed versions and the real versions. Some sort of interface specification ability (static typing, CLOS style descriptions, whatever) can help reduce these problems.
-- Cheers, The Rhythm is around me, The Rhythm has control. Ray Blaak The Rhythm is inside me, bl...@infomatch.com The Rhythm has my soul.
Gareth McCaughan <Gareth.McCaug...@pobox.com> writes: > What sort of type errors do you make that get caught in languages > with static typing?
Things like using an object in a context where it shouldn't, just because it has the same representation. E.g. A record with two integer fields can be considered a point or a rational number. If I was using a rational number in a context where a point was expected, it is most likely an error. A reasonable statically typed language would prevent such accidental (or lazy) uses. If I really intended such behaviour, I would be forced to be explicit about it, and provide conversion routines. E.g.
(let ((p (make-point 1 2)) ; Assume representation as a list of length 2 (r (make-rational 3 4))) ; Ditto (draw-to p) (draw-to r) ; Shouldn't be allowed. (draw-to (point-of r))) ; Ok, now I am being explicit
This is an example only to illustrate a type mismatches due to identical representations. Forget for the moment that real Lisp structs and rational numeric types exist.
Another common situation is getting parameter values right. E.g.
(defun make-person (name age) ...)
(let ((p1 (make-person "Joe" 5)) ; Ok. (p2 (make-person 15 "John"))) ; Not ok. ...)
Note that I don't consider C to be any sort of reasonable statically typed language. C++ can be alright if one disciplines themselves to stick the classes as much as possible and avoid the macros and low level features. The primary example of a "traditional" statically typed language is Ada, with its rich type specification ability. [Most Ada programmers are serious static typing freaks. If people think I am a zealot about this, they haven't met the real ones yet. I am distinctly mellow by comparison.]
Haskell and ML I would like to learn more about since they seem fundamentally more expressive.
> Ray Blaak wrote: > > I encounter this attitude of "fighting the compiler" often, and I > > don't understand it. [...] One does not fight the compiler per se; rather, > > one discovers the correctness of their design.
> If all languages were elegantly and consistently designed, > that might be true. But when there are gratuitous restrictions > or inconsistencies or complications in the language design, > it seems perfectly reasonable to me to speak of "fighting > the compiler" (or, maybe, "fighting the language"). [...] > I'm saying that often the best design still lumbers you with some annoyances > in implementation that could have been alleviated if the language had been > slightly different, and that these count as "fighting the compiler".
Fair enough. I was referring to bumping against restrictions of the programmer's abstractions, not language features. Using a castrated language is always frustrating.
-- Cheers, The Rhythm is around me, The Rhythm has control. Ray Blaak The Rhythm is inside me, bl...@infomatch.com The Rhythm has my soul.
Ray Blaak <bl...@infomatch.com> writes: > Gareth McCaughan <Gareth.McCaug...@pobox.com> writes: > > What sort of type errors do you make that get caught in languages > > with static typing?
> Things like using an object in a context where it shouldn't, just > because it has the same representation. E.g. A record with two > integer fields can be considered a point or a rational number. If > I was using a rational number in a context where a point was > expected, it is most likely an error. A reasonable statically > typed language would prevent such accidental (or lazy) uses. If I > really intended such behaviour, I would be forced to be explicit > about it, and provide conversion routines. E.g.
This issue is totally independent of the distinction between static vs. dynamic typing. It's a case of strong vs. weak/no typing. Sadly the two almost always get mixed up, which further diminishes the value of "discussions" about dynamic vs. static typing.
> This is an example only to illustrate a type mismatches due to identical > representations. Forget for the moment that real Lisp structs and rational > numeric types exist.
If you forget for the moment all higher level type constructors found in your favourite statically typed language, you'll get the same results, with the one distinction that the supposed type-mismatch which previously wasn't found at run-time, will now _not_ be found at compile-time, i.e. there is no difference. This tells us that the issue is about something different from dynamic vs. static typing. See above.
> Another common situation is getting parameter values right. E.g.
> (defun make-person (name age) ...)
> (let ((p1 (make-person "Joe" 5)) ; Ok. > (p2 (make-person 15 "John"))) ; Not ok. > ...)
Now here the only distinction is between dynamic vs. static typing.
OTOH since a meaningful unit test will have to cover all executable branches of code anyway, this kind of error will always be caught with no additional testing effort, as long as the types of the arguments and parameters that are mismatched are indeed different. Given the prevalence of string and integer types, this will often not be the case, in which case both static and dynamic typing will break down. This is one of the reasons why I think that environmental support (automagic display of lambda lists, online HyperSpec) is the better way to tackle issues of this sort.
Things will get a little more complicated to test when the type of a parameter depends on dynamic properties of the program:
(make-person (blabla <something dynamic>) 5)
OTOH in statically-typed languages the code will look like this:
(make-person (union-string (blabla ...)) 5)
I.e. blabla now has a declared union type, and you need to explicitly invoke the union accessor that gets you the string, for this to type-check correctly. BUT you'll still need to test, since the accessor will fail (either loudly (=> strongly-typed), or silently (=> C unions)) at run-time, if the return value isn't the string variant expected. You simply can't do better than run-time checking here.
Some B&D languages will make you put in a case construct, but since you'll only raise an error in the else case anyway, this doesn't get you more safety, but gives you more headaches. B&D.
I believe that anyone who doesn't exercise each part[1] of a unit at least once during testing deserves any bugs he gets. I also believe that static-typing would be more useful if computer programs consisted of total, non-discrete functions. Since they don't, it isn't. ;-)
Regs, Pierre.
Footnotes: [1] Note that this is different from (and easier than) exercising each possible path through a program/unit/function. The one grows linearly, the other grows exponentially.
-- Pierre Mai <p...@acm.org> PGP and GPG keys at your nearest Keyserver "One smaller motivation which, in part, stems from altruism is Microsoft- bashing." [Microsoft memo, see http://www.opensource.org/halloween1.html]
>> What sort of type errors do you make that get caught in languages >> with static typing?
> Things like using an object in a context where it shouldn't, just because it > has the same representation. E.g. A record with two integer fields can be > considered a point or a rational number.
Hmm. Using conses and lists for everything is sort of the Lisp equivalent of using |void *| for everything in C and casting every pointer every time you use it. If you do that, then you *deserve* to lose. :-)
> This is an example only to illustrate a type mismatches due to identical > representations. Forget for the moment that real Lisp structs and rational > numeric types exist.
But they do, and they do away with this kind of problem pretty completely, no? I mean, do you often define several kinds of object with the same representation? (If so, why?)
> Another common situation is getting parameter values right. E.g.
> (defun make-person (name age) ...)
> (let ((p1 (make-person "Joe" 5)) ; Ok. > (p2 (make-person 15 "John"))) ; Not ok. > ...)
I suppose the point of all this is that static typing does buy you something, but that in a well-designed dynamic language the amount it buys you is very little unless you choose to shoot yourself in the foot.
> Note that I don't consider C to be any sort of reasonable statically typed > language.
I'm very pleased to hear it! It's amusing just how much more weakly typed C is than CL. (Which is one reason why I don't like to hear people say "strong typing" when they mean "static typing". C is weakly typed and statically typed; CL is strongly typed and dynamically typed. I am not, at all, suggesting that you don't understand this.)
> C++ can be alright if one disciplines themselves to stick the > classes as much as possible and avoid the macros and low level features. The > primary example of a "traditional" statically typed language is Ada, with its > rich type specification ability. [Most Ada programmers are serious static > typing freaks. If people think I am a zealot about this, they haven't met the > real ones yet. I am distinctly mellow by comparison.]
I don't think you're a zealot about it, for what it's worth.
> Haskell and ML I would like to learn more about since they seem > fundamentally more expressive.
I'm not sure I'd go that far, but they do combine strict static typing with a good enough type inference system that you don't have to decorate everything with types as you do in C. That's certainly a good thing.
>> I'm saying that often the best design still lumbers you with some annoyances >> in implementation that could have been alleviated if the language had been >> slightly different, and that these count as "fighting the compiler".
> Fair enough. I was referring to bumping against restrictions of the > programmer's abstractions, not language features. Using a castrated language > is always frustrating.
Almost all languages are castrated to some degree, I think. I've never yet used one where I don't sometimes think "aargh, if only I could say X". Common Lisp can at least be extended when that happens (though it isn't always wise).
-- Gareth McCaughan Gareth.McCaug...@pobox.com sig under construction
Gareth McCaughan <Gareth.McCaug...@pobox.com> writes: > Ray Blaak wrote: > > Things like using an object in a context where it shouldn't, just because > > it has the same representation. [...] > > This is an example only to illustrate a type mismatches due to identical > > representations. Forget for the moment that real Lisp structs and rational > > numeric types exist.
> But they do, and they do away with this kind of problem pretty > completely, no? I mean, do you often define several kinds of > object with the same representation? (If so, why?)
If typing information is automatically part of the representation itself (i.e. some sort of type tag), then the answer is no, I don't have different objects with same representation. Otherwise, yes, its definitely possible.
Let me flip to Ada for a second:
type Seconds is range 0 .. 59; type Minutes is range 0 .. 59;
These are both integer types, both with exactly the same representation, and yet because they are different types, I cannot accidently use minutes where seconds is required, and vice versa. E.g.
declare m : Minutes; s : Seconds; begin s := 10; m := s; -- won't compile m := Minutes(s); -- ok, explicit conversion end;
To me the representation of an object is in general a private thing to the object, and a user of the object should only think in terms of the allowable operations on the object.
This is also an excellent feature that I like to have in languages. It prevents errors, makes things clearer to the reader, etc. In Ada, for example, I could say:
p2 := Make_Person (Age => 15, Name => "John");
> > Haskell and ML I would like to learn more about since they seem > > fundamentally more expressive.
> I'm not sure I'd go that far [...]
Well, more expressive in terms of specifying types is what I meant, at least compared to Ada and C++. How do they compare to CL in that regard?
-- Cheers, The Rhythm is around me, The Rhythm has control. Ray Blaak The Rhythm is inside me, bl...@infomatch.com The Rhythm has my soul.
> Ray Blaak <bl...@infomatch.com> writes: > > Things like using an object in a context where it shouldn't, just > > because it has the same representation. > This issue is totally independent of the distinction between static > vs. dynamic typing. It's a case of strong vs. weak/no typing. Sadly > the two almost always get mixed up, which further diminishes the value > of "discussions" about dynamic vs. static typing.
Well, I do indeed mean strong static typing vs strong dynamic typing.
Hmmm.
I think the issue here is this example is not really appropriate to Lisp, where objects of different types have different representations by definition. If I used real Lisp types, my example becomes good old fashioned parameter type mismatch:
(let ((p (make-point 1 2)) ; a struct (r 3/4)) (draw-to p) ; ok (draw-to r)) ; error
See my reply to Gareth for a realistic example in Ada of different types with identical representations.
> > Another common situation is getting parameter values right. E.g.
> Now here the only distinction is between dynamic vs. static typing.
> OTOH since a meaningful unit test will have to cover all executable > branches of code anyway, this kind of error will always be caught with > no additional testing effort
Not if the operation being invoked is in another unit stubbed for testing purposes, such that the stubbed version doesn't care about the parameter type. Unit tests will cover the paths through the unit but can miss this case due to the stub. Integration tests will bring the real units together, but don't necessarily cover all paths. Some sort of static specification ability will catch the bad call.
Now it all depends what you are doing, and in what language. For a smallish system the testing of all representative paths with actual units is feasible and will indeed catch the error. Large systems, on the other hand need every bit of automated error detection they can reasonably get.
> Things will get a little more complicated to test when the type of a > parameter depends on dynamic properties of the program [...] in > statically-typed languages [...] you'll still need to test
Indeed you will. Statically typed doesn't mean completely static. There is almost always some dynamic aspect possible, and run-time checking cannot be completely avoided, even in Ada. However, usually (especially with object-oriented programming) you might not know the exact type, but can assume some minimal abilities. E.g. in Java:
class Root { public void Doit () {...} }
class Descendant extends Root {...}
void DoADescendant(Descendant d) {...}
void DoARoot(Root obj) { obj.Doit(); // I don't know the exact type, but I know I can do this. DoADescendant(obj); // run-time check }
In DoARoot, know I cannot be passed anything else but an object descended from Root. Thus I don't have to test such cases such as trying with a string, an integer, a whatzit. Clients cannot accidentally call DoARoot with such cases.
Static typing is only a additional tool in a programmer's bag of useful tricks. Maybe I should really be saying "machine verifiable specification abilities".
-- Cheers, The Rhythm is around me, The Rhythm has control. Ray Blaak The Rhythm is inside me, bl...@infomatch.com The Rhythm has my soul.
Michael Hudson wrote: > Ah, but have you programmed in a language that has a *proper* types > system, like Haskell or ml? For *certain classes of problem* I find > the thinking I have to do to work out the types of things leads to > insights into the problem I probably wouldn't otherwise have had.
Nope. I've looked at the docs and sample code for them and figured they're not my style, though I understand for some people they work very well.
-- "To summarize the summary of the summary: people are a problem." Russell Wallace mailto:manor...@iol.ie
Ray Blaak wrote: >>> This is an example only to illustrate a type mismatches due to identical >>> representations. Forget for the moment that real Lisp structs and rational >>> numeric types exist.
>> But they do, and they do away with this kind of problem pretty >> completely, no? I mean, do you often define several kinds of >> object with the same representation? (If so, why?)
> If typing information is automatically part of the representation itself > (i.e. some sort of type tag), then the answer is no, I don't have different > objects with same representation. Otherwise, yes, its definitely possible.
> Let me flip to Ada for a second:
> type Seconds is range 0 .. 59; > type Minutes is range 0 .. 59;
> These are both integer types, both with exactly the same representation, and > yet because they are different types, I cannot accidently use minutes where > seconds is required, and vice versa. E.g.
OK, that's a good example. I like it.
A question, though: does the decrease in testing time that comes from declaring all your counts of minutes and seconds explicitly make up for the increase in coding time that comes from, er, declaring all your counts of minutes and seconds explicitly?
Actually, this particular example isn't so great because the Right Answer is almost certainly not to have separate types for minutes and seconds, but to have objects for holding times, or time intervals, of which minutes and seconds would just be special cases. But there are other parallel examples where similar options aren't available. (Distances and angles, maybe.)
These examples make me wonder whether there's anything to be said for giving a programming language a notion of *units*, so that (+ (minutes 5) (seconds 5)) is (seconds 305) and (+ (metres 10) (square-metres 10)) is a type error. It's probably just much, much too painful. :-)
(There are systems that let you do things like this. For instance, the "Mathcad" package does.)
> To me the representation of an object is in general a private thing to the > object, and a user of the object should only think in terms of the allowable > operations on the object.
I agree. But I wonder how far you really want to take it. Distinguish between a positive integer that describes the length of a list and another that describes the length of an array, perhaps? Or between indices into two different arrays? Should two cons cells be of different types, if the CAR of one is a symbol representing a kind of fruit and the CAR of the other is a symbol representing a kind of bread? That way, I think, lies madness...
> This is also an excellent feature that I like to have in > languages. It prevents errors, makes things clearer to the reader, > etc. In Ada, for example, I could say:
> p2 := Make_Person (Age => 15, Name => "John");
One of the things I really hate about C and C++ is that they have no keyword arguments.
>>> Haskell and ML I would like to learn more about since they seem >>> fundamentally more expressive.
>> I'm not sure I'd go that far [...]
> Well, more expressive in terms of specifying types is what I meant, at least > compared to Ada and C++. How do they compare to CL in that regard?
Well, obviously, *nothing* is more expressive than CL in any regard whatever. :-)
In ML there are types like "list of integers" or "function that, given an object of any type, returns a list of objects of that type". CL's type system doesn't have those. On the other hand, I don't think ML has the "list of objects of any types" type.
-- Gareth McCaughan Gareth.McCaug...@pobox.com sig under construction
Gareth McCaughan <Gareth.McCaug...@pobox.com> writes: > OK, that's a good example. I like it.
> A question, though: does the decrease in testing time that > comes from declaring all your counts of minutes and seconds > explicitly make up for the increase in coding time that > comes from, er, declaring all your counts of minutes and > seconds explicitly?
There is the up front work of declaring the types and their allowable operations. Once that is done, actually using the types is not really much more work than using the default types.
The payoff is not so much a decrease in testing time. It is substantial decrease in development time in general. The Ada development cycle tends to be compile->link->run with the occasional foray into the debugger. C++, by comparison is more like compile->link->whoops, unresolveds!->link->debug-> debug->debug->...->eventually run for real.
On the other hand, Ada is fundamentally a compiled language, with a mandatory analysis phase. An interpreted version of Ada would be tedious to use indeed.
Ray Blaak wrote: > > To me the representation of an object is in general a private thing to the > > object, and a user of the object should only think in terms of the > > allowable operations on the object.
> I agree. But I wonder how far you really want to take it.
In the Ada mindset one tends to take things pretty far. The guiding principle is that abstractions that are not supposed to interfere with each other will not, at least not implicitly. For abstractions that are supposed to deal with each other, one constructively specifies what one is allowed to express.
But this is not an Ada newsgroup, and I think I have said enough on the matter.
-- Cheers, The Rhythm is around me, The Rhythm has control. Ray Blaak The Rhythm is inside me, bl...@infomatch.com The Rhythm has my soul.