[comp.lang.lisp] Common Lisp lacks portability

ruffwork@orstcs.CS.ORST.EDU (Ritchey Ruff) (12/08/87)

Would you use a language that can arbitrarily ignore some of your
code ???  Especially if different implementations ignored different
statements in the same code ???  Even if it didn't TELL you what
it was ignoring when ???

I have a bone to pick with Steele about something he left out of
the Common Lisp definition.  The above is *EXACTLY* what Common
Lisp *DOES* !!! In the sections about the
strong typing, "Common Lisp:The Language" says the compiler 
or interpreter can ignore many declarations.  It should also 
state that there be a standard way to find out WHAT the compiler/
interpreter is ignoring (or using).  Something like a compiler flag 
(":declares-ignored t/nil") or a global flag (*IGNORED-WARNINGS*) to 
force common lisp to show what it is ignoring.

Why, you ask???  First, principle (I kind of like that ;-):
when you put in strong typing statements (like "(the integer foo)")
do you REALLY want them ignored in different ways, at different times,
by different Common Lisps - and not even know which is ignoring
what when ??? 

Second, I've just spent weeks tracking bugs caused by 
compilers/interpreters ignoring different parts of my declarations.  Simply
because an interpreter/compiler can IGNORE strong typing (like 
"(the integer foo)"), optimizer statements (like safety=3), 
and declarations (like "(declare (integer foo))") I found that
code that ran ok on one version of Common Lisp would not even
compile under another, run but go into a break on another, and
run to completion but give wrong results on another !!!!

For example - lots of people use Bill Shelters' excellent SLOOP
looping macro package (thanks for all that work you put into an
excellent package, Bill!).  Its great, but because it tries to
optimize (by default it expands with declarations that give
type info on looping vars, etc.) it turns out to be non-portable.  
Here is a totally non-portable piece of code -

	(DEFUN TST (N M)
	       (SLOOP FOR I FROM N TO M COLLECT I))

This is quite simple, right?  When it expands N, M, and I get
declared of type integer, and the iteration var gets checked by 
the "THE" statement each time it's incremented to see that it
remains of type integer.  Below are results from several different
Common Lisps (all this was done with safety=3) ---

	----------------------------------------
	FranzExtendedCommonLisp> (tst 1 5)
		(1 2 3 4 5)

	FranzExtendedCommonLisp> (tst 1.0 5.0)

	Continuable Error: Object 2.0 is not of type FIXNUM.
	If continued with :continue, Prompt for a new object.
	[1c] <cl> ^D

	FranzExtendedCommonLisp> (compile 'tst)
	TST
	FranzExtendedCommonLisp> (tst 1.0 5.0)
		(1.0 2.0 3.0 4.0 5.0)

	FranzExtendedCommonLisp> (tst 1 5.0)
		(1 2 3 4 5)
	----------------------------------------
	KyotoCommonLisp> (tst 1 5)
		(1 2 3 4 5)

	KyotoCommonLisp> (tst 1.0 5.0)

		Error: 2.0 is not of type FIXNUM.
		Error signaled by THE.

		Broken at THE.  Type :H for Help.
	KyotoCommonLisp>> :q
	KyotoCommonLisp> (compile 'tst)
		End Pass1.
		End Pass2.

		TST
	KyotoCommonLisp> (tst 1.0 5.0)
		(0)
	KyotoCommonLisp> (tst 1 5.0)
		NIL
	----------------------------------------
	AllegroCommonLisp> (tst 1 5)
		(1 2 3 4 5)
	AllegroCommonLisp> (tst 1.0 5.0)
		(1.0 2.0 3.0 4.0 5.0)
	AllegroCommonLisp> (compile 'tst)
		TST
	AllegroCommonLisp> (tst 1.0 5.0)
		(1.0 2.0 3.0 4.0 5.0)
	AllegroCommonLisp> (tst 1 5.0)
		(1 2 3 4 5)
	----------------------------------------

So we have 3 different "Common Lisps" (and the quotes are intentional)
that give radically different results for the SAME code !!!  EVEN the
interpreter (Help me, Spock ;-) !!!  If the compiler and interpreter
gave warnings when they ignored code the reason for the bugs that this type
of behavior can cause would be so much easier to track down.
When you have your code debugged and are looking for raw speed, 
a global flag could be set to stop displaying warnings of this type.

MORAL OF THE STORY --- IF YOU WANT TRULY PORTABLE COMMON LISP CODE 
	THAT WORKS THE SAME INTERPRETED AS COMPILED, *DO* *NOT* PUT 
	STRONG TYPING OR OPTIMIZER STATEMENTS ANYWHERE IN YOUR CODE !!!
	IF *ANYTHING* *CAN* IGNORE A STATEMENT, *NEVER* USE THAT STATEMENT !!!

I've gone on too long, but I think I've made my point.
Thanks for the bandwidth,

--Ritchey Ruff				ruffwork@cs.orst.edu -or-
 "I haven't lost my mind,		ruffwork%oregon-state@relay-cs-net -or-
  its' backed up on tape somewhere..."	{ hp-pcd | tektronix }!orstcs!ruffwork

quiroz@cs.rochester.edu (Cesar Quiroz) (12/08/87)

Expires:

Sender:

Followup-To:


(Context:  Ritchey Buff complains about the freedom given to the
implementors to ignore certain elements of the language
(declarations) without any warning to the user.)

First of all, I agree that each implementation should tell you which
declarations are ignored and how the used ones affect your code.  (I
know of at least one implementation where declarations may slow down
correct code.)  Indeed, all the implementations I know of at least
try, so I don't see why the language specification is in fault.  You
need the info to decide on efficiency matters, but the info is often
provided anyway.

What prompts me to follow-up is the conclusion of the article.
Notice carefully that you cannot demand that any implementation tag
arbitrarily *incorrect* code.  You can, however, compare the quality
of the attempt: If an implementation catches more errors than other,
the first one is probably the one you desire most.

So one cannot build an argument around *incorrect* code.  (Your
subject line should have been `Incorrect Common Lisp code lacks
portability'.  We suspected that much long ago.)  Your example was
just wrong, the fact that one implementation missed it is
unfortunate and, at most, reflects on that implementation.  And
without the declarations, none of the implementations you tested
would have given you any error message.  The conclusion should be
the opposite: if you can, provide more declarations as you improve
your code.  High quality implementations may use that information to
help you debug, lesser ones should still do something useful when
your code is correct.  What you propose is that, because some
implementations cannot catch some errors, we should avoid
discovering those bugs in all the implementations.

To sum: Declarations can affect (perhaps negatively!) the efficiency
of correct code, but never make a correct program incorrect.
Declarations do not make incorrect code correct.  The behavior of
incorrect code (other than some error reporting when it is
explicitly guaranteed that `an error is signalled') is, of course,
undefined and well up to the implementation.  And decent
implementations document the decisions left up to them.


-- 
Cesar Augusto  Quiroz Gonzalez
Department of Computer Science     ...allegra!rochester!quiroz
University of Rochester            or
Rochester,  NY 14627               quiroz@cs.rochester.edu

gz@spt.entity.com (Gail Zacharias) (12/10/87)

Common Lisp does provide a number of ways to portably request type checking
at run time, such as CHECK-TYPE and ASSERT, etc.  But it doesn't specify the
behavior of incorrect programs (such as programs in which declarations are
false).  Basically, the debugging environment is up to the implementation.  I
don't think that's a major shortcoming of the Common Lisp standard.  The
important thing is that once your program is debugged, it should run in any
Common Lisp implementation.

(Btw, if by Allegro Common Lisp you meant the Macintosh one, i.e. Coral
Common Lisp, then here's a hint: turn off *compile-definitions*!  The CCL
evaluator does indeed signal an error on incorrect THE forms.  The reason you
didn't get an error in your example is that TST was running compiled.)

-------
gz%entity.com@eddie.mit.edu	  gz@entity.com		{backbone}!spt!gz

preece@ccvaxa.UUCP (12/12/87)

  ruffwork@orstcs.CS.ORST.EDU:
> Would you use a language that can arbitrarily ignore some of your code
> ???  Especially if different implementations ignored different
> statements in the same code ???  Even if it didn't TELL you what it was
> ignoring when ???
----------
Well, C can ignore register declarations.

Declarations in CL are similarly supposed to be YOUR promise that the
named objects will be of a certain type, ALLOWING the system to make
that assumption if it is convenient for it to do so.

The behavior of CL under many kinds of errors is not completely
required.  Why does it offend you so much that when you lie to different
CLs (by providing non-integer arguments) different things happen?
If you aren't willing to promise not to lie, you shouldn't use
declarations.

-- 
scott preece
gould/csd - urbana
uucp:	ihnp4!uiucdcs!ccvaxa!preece
arpa:	preece@Gould.com

ross@ulowell.cs.ulowell.edu (Ross Miller) (12/15/87)

In article <233@spt.entity.com> gz@spt.entity.com (Gail Zacharias) writes:
>Common Lisp does provide a number of ways to portably request type checking
>at run time, such as CHECK-TYPE and ASSERT, etc.  But it doesn't specify the
>behavior of incorrect programs (such as programs in which declarations are
>false).  Basically, the debugging environment is up to the implementation.  I
>don't think that's a major shortcoming of the Common Lisp standard.  The
>important thing is that once your program is debugged, it should run in any
>Common Lisp implementation.

Wrong.  Crreeeeaaaak, sound of Common lisp tomb being opened yet again, I
have read and read, and reread that section looking for a way out.  An
implementation is free to ignore type checking and not return any errors,
but it must except the code that declares type checking.  That is not a
quote, but it is the jist.  Here is a case where a program could be perfectly
debugged and still fail.
	What if we had an implemenation of GKS for lisp.  This is being worked
on by X3H3.4 of ANSI.  The lisp is actually an interface to another language
that the GKS is really written in.  The GKS is perfect passes all the test
suites etc.  But now an application programmer sits down to actually use
this beast.  By accident he sends a float to polyline when an integer
was expected.  Lisp then passes the 32bit word down to say C, machine size
may vary, and C blithly trys to write out a few million polylines, or some
other random number or worse a negative array reference, and the OS 
informs us of an array overflow.  Please keep in mind the actual error
could vary greatly depending on what the final GKS spec looks like and
what the implentation actually does but you get the idea.
	If the GKS is actually implemented in lisp, then lisp will return
an error eventually from such garbage being passed to it, and again the
applications programmer is going to have to intuitivly figure out what
is wrong.  Worse, nothing is wrong the GKS is perfect only his program is
wrong but he will get errors unrelated to what is wrong because data with
garbage types will be being passed around.
	In fact the resolution of the committee was garbage in garbage out.
An implentation is free to check the types manually upon invokation of
the function but is not required to do so.  This was resolved so that
code could continue to be portable across GKS applications in different
lisps.
	On another subject have I missed the argument on why arrays are not 
always passed by value?

								Ross




-- 
csnet: ross@ulowell.cs.ulowell.edu
uucp:  ross@ulowell.cs.ulowell.edu || ...harvard!ulowell!ross

Trust the computer.	The computer is your friend.

quiroz@cs.rochester.edu (Cesar Quiroz) (12/15/87)

In article <2126@ulowell.cs.ulowell.edu>, Ross Miller offers to show
an example of a `debugged' program that stills fails.  I emphasized
`debugged', because that wasn't the issue.  Here the problem is
whether the program is correct or not, not whether it has been
debugged, whatever that means.  Miller's program was in error, so
his objection is moot. (I hope I am not quoting his posting out of
context due to my heavy editing):

: ... Here is a case where a program could be perfectly
:debugged and still fail.
:	What if we had an implemenation of GKS for lisp.  ...
:...  The GKS is perfect passes all the test suites etc. ...
:By accident he sends a float to polyline when an integer
:was expected...

The scenario involves at least this incorrection.  Then, the program
is not correct, never mind how energetically debugged.  Never mind
CL does not specify how to pass things to a C program, etc.  These
latter questions are of the highest practical importance, see below,
but shouldn't cloud our judgement as to the fact that the program
was in error and whatever happens was deserved.)

:	If the GKS is actually implemented in lisp, then lisp will return
:an error eventually from such garbage being passed to it, and again the
:applications programmer is going to have to intuitivly figure out what
:is wrong.  Worse, nothing is wrong the GKS is perfect only his program is
:wrong but he will get errors unrelated to what is wrong because data with
:garbage types will be being passed around.

I feel reasonable sympathy for such programmer.  It is still true
that `only his program is wrong'.  And that is all that is needed to
render the objection moot.

On the issue of interfacing with other languages, I think it is
clear there is no way to legislate a solution.  Good ideas should be
tried, in the hopes that almost de-facto standards for specific
combinations of languages and architectures eventually emerge.  If
Lispers want to carve a nice niche for their favorite language in
the numeric community, such interfaces will be needed.  (But CL
already has a better defined arithmetic than many other languages,
just improving the Lisp compilers might be a better idea.  Burn
those dusty decks :-)


-- 
Cesar Augusto  Quiroz Gonzalez
Department of Computer Science     ...allegra!rochester!quiroz
University of Rochester            or
Rochester,  NY 14627               quiroz@cs.rochester.edu

alex@umbc3.UMD.EDU (Alex S. Crain) (12/15/87)

In article <2126@ulowell.cs.ulowell.edu> ross@swan.cs.ulowell.edu (Ross Miller) writes:
>	What if we had an implemenation of GKS for lisp.

  [much deleted about how programs that use incorrect data give poor results]

[Lessee, I think I got a match here somewhare...there we go]
I think that the problem here is that the wrong language is being used. Might
I suggest VAX/VMS Pascal? VAX Pascal offers a multitude of idiot-proofing
schemes to protect incompetant programmers from themselves, includeing run
time bounds checking and full checking of types, use checking of variables,
etc. True, all that extra work causes the code to run at about 1/2 speed at
best, but it seems to supply what you want.

	I play consultant at the first-aide desk in the computer center
a few hours a week, for the sake of the distraction, and I get mutitudes
of people (more seniors than I would like) with the attitude that the 
language is responsibe for detection of programmer stupidity. My answer
to them is the same as the one above. If you can't hack working without
a net, get a net and suffer the consiquences of poor run time performance
and code restriction. But don't blame the language for your mistakes.

[flame off]

	There is a valid point here though, in that it is in general difficult
to find obscure semantic errors in an interpreted environment. I was thinking
is what I really want is lint for lisp. I was tracing through some code (mine)
recently and found..

	(prog						(progn
	   (arf narf snarf)      as opposed to		   (arf narf snarf)
		...						...
	   )						    )

which didn't cause major problems, but the variable scoping was wrong and
(arf ...) was never evaled. I would have rather had something major.

	I would guess that lint for lisp wouldn't be too far fetched,
a utility that would complain about unused variables and wrong # of arguments,
etc. My compiler does this but that is so much overhead that its a bit
extreme. Has anyone ever seen such an animal? Realizing that it would
have to be tuned for each dialect, source for such a thing in any form that
could be modified for use with CL would certainly be a help.

					:alex.


alex@umbc3.umd.edu

ram@WB1.CS.CMU.EDU (Rob MacLachlan) (12/16/87)

>Wrong.  Crreeeeaaaak, sound of Common lisp tomb being opened yet again,

Eeeeee-hah!  What fun!  Even MMister JJacobs has crawled back on the net, 
dragging his odious slime-trail behind him.  But I won't lower myself by
responding to his message.  Instead I will flame about compilers and
declarations in Common Lisp, which Common Lisp fans know is one of my
favorite topics.

First of all, I point out that Common Lisp is a standard for writing
portable programs.  It is possible to write programs that run in particular
Common Lisp implementation, yet are not Common Lisp programs.

The declaration mechanism in Common Lisp provides a way to make many
interesting assertions about a program.   As a special case, it allows
programmers to make untrue assertions.

There are a number of areas in which "Common Lisp the Langauge" is vague
about what constitutes a correct Common Lisp program, but the aspect of type
declarations currently under discussion is not one of them.

I think the real complaint here is:
  "Common Lisp has features that support unsafe (and efficient) compilation."

This is true, but it should be up to the programmer to decide whether he
wants to risk unsafe compilation.  This is why Common Lisp proves the
OPTIMIZE declaration -- to allow the programmer to tell the compiler how to
evaluate speed/space/safety/compilation-speed tradeoffs.

In the abstract, the Common Lisp OPTIMIZE declaration provides better
control over these tradeoffs than any other Lisp dialect I know of.
Unfortunately, as realized by existing implementations, this capability is
largely lacking.

The most unfortunate property of existing Common Lisp compilers is their
assumption that a type declaration implies the generation of unsafe code.
There is nothing in Common Lisp that suggests this: it is merely an evil 
vestiage of traditional Lisp compilation.  I believe that a Common Lisp
compiler should have some mode in which it actually emits extra code to
verify all type declarations.  This is a policy decision, controlled by the
OPTIMIZE declaration, and not an issue of language semantics.

Even without full type checking, the Common Lisp programmer is no worse off
than the programmer in any other dialect.  He can avoid the risk of
incorrect type assertions by not making any.

The real reason that people flame about Common Lisp portability is a
paradoxical one: Common Lisp is the first Lisp dialect that has been
implemented many different times.  People move their "Common Lisp" programs
from one implementation to another, expecting them to run without a hitch.
When they hit a snag, they snivel and whine.  They never had this problem
with XYZZYLisp (of course, it only ran on one machine).

I won't argue that it is impossible (or even difficult) to come up with a
Lisp dialect that makes it harder to write non-portable code.  Type
declarations are an obvious source of non-portability; they were included in
Common Lisp because efficiency was also a major goal.  

If anyone has suggestions for ways to make Common Lisp more portable without
serious adverse effects on other design goals, then we would be glad to hear
them.  It is not acceptable to either require declarations to be checked or
to forbid declarations, since both would cause serious efficiency problems.

Common Lisp can strongly suggest that the compiler offer a mode which does
full type checking, but it would be inconsistent with the language design
philosophy to require it.  In Common Lisp terms, this is an "environment
issue".  A given implementation can do whatever it wants to help programmers
write correct programs, but once written, these programs should run in an
environment that implmements only the features described in the manual.

    Rob MacLachlan (ram@c.cs.cmu.edu)
    CMU Common Lisp  --  Making the world safe for symbolic computing.

jeff@aiva.ed.ac.uk (Jeff Dalton) (12/20/87)

In article <2126@ulowell.cs.ulowell.edu> ross@swan.cs.ulowell.edu (Ross Miller) writes:
>An implementation is free to ignore type checking and not return any errors,
>but it must except the code that declares type checking.  That is not a
>quote, but it is the jist.  Here is a case where a program could be perfectly
>debugged and still fail.

Here is a way to think about type checking in Common Lisp:

 * Assume there is no type checking unless you explicitly code it
   by writing calls to CHECK-TYPE or your own tests and error calls.
   This assumption may well be correct for compiled code in some
   implementations.

   Note that CHECK-TYPE is a way to signal an error, not a declaration.
   An implementation is not free to ignore it.

 * Write your code with enough explicit checks so that users cannot
   confuse it with incorrect data.  Use any additional internal checks
   that you feel are needed.

   Here I am assuming that only certain routines will be part of the
   external interface.  You can enforce this by using packages (yes, I
   know one *can* still call internal procedures) or by using FLET
   and LABELS to keep internal routines local.
 
 * In cases where you are sure the types are correct, and where the
   speed of the compiled code is important, add declarations to inform
   the compiler of the types involved.  It may then generate type-specific
   but more efficient code.

   Since you have actually checked the types somewhere, and have done
   a bit of type inference, you can be confient that the declarations
   are safe.

Code of this sort will be portable.

>On another subject have I missed the argument on why arrays are not 
>always passed by value?

Are they sometimes not passed by value?  Note that in Lisp objects,
such as arrays, aren't copied when passed as parameters: in another
language, they would be pointers.

Jeff Dalton,                      JANET: J.Dalton@uk.ac.ed             
AI Applications Institute,        ARPA:  J.Dalton%uk.ac.ed@nss.cs.ucl.ac.uk
Edinburgh University.             UUCP:  ...!ukc!ed.ac.uk!J.Dalton

roberts@cognos.uucp (Robert Stanley) (12/24/87)

In article <2126@ulowell.cs.ulowell.edu> ross@swan.cs.ulowell.edu
	   (Ross Miller) writes:

>Wrong.  Crreeeeaaaak, sound of Common lisp tomb being opened yet again, I
>have read and read, and reread that section looking for a way out.  An
>implementation is free to ignore type checking and not return any errors,
>but it must except the code that declares type checking.  That is not a
             ^  ^
	     |  `-  EXCEPT, meaning make an exception == not accept?
	     `----  ACCEPT, indicating text is a misprint?
>quote, but it is the jist.  Here is a case where a program could be perfectly
>debugged and still fail.

Sorry, it's Christmas eve, I'm waiting for the compiler guys to finish printing
86 separate source files, and I read the above statement several times before
admitting that I couldn't discriminate.
>-- 
>csnet: ross@ulowell.cs.ulowell.edu
>uucp:  ross@ulowell.cs.ulowell.edu || ...harvard!ulowell!ross

>Trust the computer.	The computer is your friend.
Yah, computers never fai
-- 
R.A. Stanley  - roberts@cognos.uucp
uucp: decvax!utzoo!dciem!nrcaer!cognos!roberts       Compuserve: 76174,3024
baud, bawd, bard, bared, bored, bore, bode, bade, bare, bar, boar, board
intelligence at 1200 bps!

shebs%defun.utah.edu.uucp@utah-cs.UUCP (Stanley T. Shebs) (01/21/88)

In article <2126@ulowell.cs.ulowell.edu> ross@swan.cs.ulowell.edu (Ross Miller) writes:

>[...] By accident he sends a float to polyline when an integer
>was expected.  Lisp then passes the 32bit word down to say C, machine size
>may vary, and C blithly trys to write out a few million polylines, or some
>other random number or worse a negative array reference, and the OS 
>informs us of an array overflow. [...]

If anybody is surprised by all this, mail me and I'll send you a catalog
of palaces in England I've been handling.

There has been talk of doing "safe" interfaces between Lisp and C, but as
long as I can do *(12345) in the address space of my Lisp system, nothing
is safe.  I can get a little help by using an interface that checks types,
but it's going to be a dog, no way around it.

>	On another subject have I missed the argument on why arrays are not 
>always passed by value?

Oh yeah, I like making copies of million-byte arrays every time I pass them
to a function.  Just think, I would get a copy with every call to AREF!
That would show those purely functional weenies what *real* storage allocation
is like!  :-) :-)

							stan shebs
							shebs@cs.utah.edu