From: Kleanthes Koniaris
Subject: Re: declare
Date: 
Message-ID: <35284@dime.cs.umass.edu>
Dear Readers:

Is there any way for a macro to know the declared type of a symbol or
form that it takes as an argument?  For example, let's say that I
write

   (let ((x 3.0) (y 2.0))
     (declare (type single-float x y))
     (my-macro x y))

Then, inside my-macro....

   (defmacro my-macro (a b)
     (case (declared-type-of a)
      ((single-float) ....)
      (otherwise ....)))

In short, I am wondering how one could define the function
declared-type-of.

There are many examples where I would find this function useful.
Consider a "safe" setf, ssetf:

  (defmacro ssetq (thing value)
     (let ((declared-type (declared-type-of thing)))
       (if (eq declared-type t) ; a bit too general....
	   `(setf ,thing ,value)
	   `(setf ,thing (the ,declared-type ,value)))))

If "the" flags errors in type, this would make a lot of *my* code more
robust!  (I often break code when I crank it up to high optimization,
and it often takes me hours to find my mistakes; I like to do physics
in Common Lisp, so I need to compile with high optimization [or I'd
get old waiting for results, and have to turn to evil C++].)

Perhaps declared-type-of could return t if there was no declaration.

I thank any experts for their advice.

          --kleanthes (···@gang.umass.edu)

From: Eliot Handelman
Subject: Re: declare
Date: 
Message-ID: <13540@idunno.Princeton.EDU>
In article <·····@dime.cs.umass.edu> ···@gang.umass.edu (Kleanthes Koniaris) writes:
;
;Is there any way for a macro to know the declared type of a symbol or
;form that it takes as an argument?  For example, let's say that I
;write
;
;   (let ((x 3.0) (y 2.0))
;     (declare (type single-float x y))
;     (my-macro x y))
;
;Then, inside my-macro....
;
;   (defmacro my-macro (a b)
;     (case (declared-type-of a)
;      ((single-float) ....)
;      (otherwise ....)))
;
;In short, I am wondering how one could define the function
;declared-type-of.


That's easy. Simply pass the type along with the other macro args
and have the macro expand accordingly. 

Something like this will do:

(let ((x 3.0))
  (declare (short-float x))
  (my-macro x :short-float)) ;; 'short-float might cause package problems

(defmacro my-macro (arg type)
   (case type
     (:fixnum (expand-my-macro-for-fixnums arg))
     (otherwise ...)))

If the point is merely to insert a lot of declarations in the macro,
it's probably more straightforward to splice dclarations into a template,
something like this:

(defmacro my-macro (arg type-key)
  (let ((type (case type-key (:fixnum 'fixnum) (:otherwise t))))
    `(do-special-operations (the ,type ,a))))
From: Barry Margolin
Subject: Re: declare
Date: 
Message-ID: <1991Aug25.212240.9910@Think.COM>
In article <·····@dime.cs.umass.edu> ···@gang.umass.edu (Kleanthes Koniaris) writes:
>Is there any way for a macro to know the declared type of a symbol or
>form that it takes as an argument?

No.
-- 
Barry Margolin, Thinking Machines Corp.

······@think.com
{uunet,harvard}!think!barmar
From: Doug Morgan
Subject: Re: declare
Date: 
Message-ID: <DOUG.91Aug26113207@monet.ads.com>
In article <·····@dime.cs.umass.edu> ···@smectos.gang.umass.edu (Kleanthes Koniaris) writes:
   ...
   Is there any way for a macro to know the declared type of a symbol or
   form that it takes as an argument?  ...

I was going to suggest replacing the declare with your own macro that
puts the declared variables in a compile-time symbol table annotated
with types.  Unfortunately, pg. 217 of CLtL2 indicates this isn't ok
anymore.  What a mistake.

Actually, the real mistake is not having built-in support for querying
for the declared type, whether the declaration is in a declare or
argument specializer.  This makes it impossible to write nicely
packaged macros that produce efficient code --- you always wind up
having to add extra stuff to tell the macro about types (eventhough
the compiler, or anyone glancing at the code, knows exactly what the
types are).

Doug Morgan
Advanced Decision Systems
Mountain View, CA 94043-1230
(415) 960-7300  ····@ads.com
From: Barry Margolin
Subject: Re: declare
Date: 
Message-ID: <1991Aug26.214454.23864@Think.COM>
In article <··················@monet.ads.com> ····@monet.ads.com (Doug Morgan) writes:
>In article <·····@dime.cs.umass.edu> ···@smectos.gang.umass.edu (Kleanthes Koniaris) writes:
>I was going to suggest replacing the declare with your own macro that
>puts the declared variables in a compile-time symbol table annotated
>with types.  Unfortunately, pg. 217 of CLtL2 indicates this isn't ok
>anymore.  What a mistake.

Yes, we understood that this feature could be used in this way when we
removed it.  The rationale given in the proposal to remove it was:

  The ability to have a macro form expand into a declaration has been shown
  to be useful in some situations.  More often, however, the presence of
  this feature has been seen to cause problems elsewhere in the language.
  Ultimately, its benefits have not outweighed its costs.

We made this change with the knowledge that there are usually alternatives
to macros that expand directly into declarations.  For instance, you can
use a macro that expands into the entire binding form, including the
declarations.

>Actually, the real mistake is not having built-in support for querying
>for the declared type, whether the declaration is in a declare or
>argument specializer.  This makes it impossible to write nicely
>packaged macros that produce efficient code --- you always wind up
>having to add extra stuff to tell the macro about types (eventhough
>the compiler, or anyone glancing at the code, knows exactly what the
>types are).

We tried to do this.  In fact, CLtL2 documents the environment querying and
augmenting functions that could be used to do what the original user
wanted.  However, we kept on having to change the design of this mechanism,
and finally realized that we were trying to standardize and design this at
the same time, and getting ahead of ourselves, so we gave up and removed it
from the language.
-- 
Barry Margolin, Thinking Machines Corp.

······@think.com
{uunet,harvard}!think!barmar
From: Rob MacLachlan
Subject: CL type support (was Re: declare)
Date: 
Message-ID: <1991Aug26.171139.104331@cs.cmu.edu>
> let's say that I write
>
>   (let ((x 3.0) (y 2.0))
>     (declare (type single-float x y))
>     (my-macro x y))
>
>Then, inside my-macro....
>
>   (defmacro my-macro (a b)
>     (case (declared-type-of a)
>      ((single-float) ....)
>      (otherwise ....)))
>

A good compiler should be able to handle:
(declaim (inline myfun))
(defun myfun (a b)
  (typecase a
    (single-float ...)
    (t ...)))

>There are many examples where I would find this function useful.
>Consider a "safe" setf, ssetf:
>
>  (defmacro ssetq (thing value)
>     (let ((declared-type (declared-type-of thing)))
>       (if (eq declared-type t) ; a bit too general....
>	   `(setf ,thing ,value)
>	   `(setf ,thing (the ,declared-type ,value)))))
>
>If "the" flags errors in type, this would make a lot of *my* code more
>robust!

A good compiler should always check declarations under safe policies.  
However, so far as I know, only the CMU CL compiler (Python) is that good.

>(I often break code when I crank it up to high optimization,
>and it often takes me hours to find my mistakes; I like to do physics
>in Common Lisp, so I need to compile with high optimization [or I'd
>get old waiting for results, and have to turn to evil C++].)

A good compiler should only have minor slowdowns in safe numeric code.  Here
are the speedups (selected Gabriel benchmarks) of CMU CL v.s. Allegro on a
DECStation 3100, under fast and safe policies:

         Benchmark   Allegro/CMU (fast)  Allegro/CMU (safe)
               Fft                1.55               33.04
            Puzzle                1.08                5.71
               Tak                1.02                1.74
              Ctak                1.06                1.25
            Dderiv                0.79                1.28
       Destructive                1.50                2.00
 Run-Init-Traverse                2.22                2.88
  Run-Run-Traverse                0.81                2.65
            Triang                1.08                2.57


Actually, there was a proposal to allow macros access to type declarations
and other compiler environment information, but it was too experimental to
be accepted in this standardization round.

p.s.  I just posted a CMU CL intro on comp.lang.clos, so I won't repeat it
here.  Anyone doing number crunching in Lisp will be very interested, since
Python gets its biggest speedups in numeric code.

  Rob
From: Bob Kerns
Subject: Re: CL type support (was Re: declare)
Date: 
Message-ID: <1991Aug26.223642.23736@crl.dec.com>
I had a few problems following what you were getting at in your
message...

In article <·······················@cs.cmu.edu>, ····@cs.cmu.edu (Rob MacLachlan) writes:
> > let's say that I write
> >
> >   (let ((x 3.0) (y 2.0))
> >     (declare (type single-float x y))
> >     (my-macro x y))
> >
> >Then, inside my-macro....
> >
> >   (defmacro my-macro (a b)
> >     (case (declared-type-of a)
> >      ((single-float) ....)
> >      (otherwise ....)))
> >
> 
> A good compiler should be able to handle:
> (declaim (inline myfun))
> (defun myfun (a b)
>   (typecase a
>     (single-float ...)
>     (t ...)))

I think you're expecting the compiler to do type-inferencing
to deduce that only one of the typecase clauses are needed.
However, in the practical world of existing compilers, it
isn't going to help you, because they won't.

Too bad, because this is the cleanest technique.

> A good compiler should always check declarations under safe policies.  
> However, so far as I know, only the CMU CL compiler (Python) is that good.

On my first reading, I mis-interpreted this, I think.  I think you're
saying that, when the OPTIMIZE declaration calls for safe code, they
should always enforce the claims made by the declarations.  This
would certainly help catch a lot of problems, and would be especially
helpful in writing portable code.

> A good compiler should only have minor slowdowns in safe numeric code.  Here
> are the speedups (selected Gabriel benchmarks) of CMU CL v.s. Allegro on a
> DECStation 3100, under fast and safe policies:

This is key; users should not need to disable checking except in
small, well-tested parts of the code.  The vast majority of
code just isn't executed frequently enough to benefit, and the
risk of turning off on large bodies of code is just too high
to ever be worth it.
From: Rob MacLachlan
Subject: Re: CL type support (was Re: declare)
Date: 
Message-ID: <1991Aug28.235618.126631@cs.cmu.edu>
In article <······················@crl.dec.com> ···@crl.dec.com writes:
>> 
>> A good compiler should be able to handle:
>> (declaim (inline myfun))
>> (defun myfun (a b)
>>   (typecase a
>>     (single-float ...)
>>     (t ...)))
>
>I think you're expecting the compiler to do type-inferencing
>to deduce that only one of the typecase clauses are needed.
>However, in the practical world of existing compilers, it
>isn't going to help you, because they won't.
>
>Too bad, because this is the cleanest technique.

Hmmn, I thought Lucid might handle this easy case.  In any case, Python does.
Python can also deal with more complex inferences like:
  (defun foo (x)
    (declare (type (or single-float null) x))
    (if x 
        (myfun x x)
        ...))
>
>> A good compiler should always check declarations under safe policies.  
>> However, so far as I know, only the CMU CL compiler (Python) is that good.
>
>On my first reading, I mis-interpreted this, I think.  I think you're
>saying that, when the OPTIMIZE declaration calls for safe code, they
>should always enforce the claims made by the declarations.  

Yes, that's what I mean.

>This
>would certainly help catch a lot of problems, and would be especially
>helpful in writing portable code.

Yes, it does.  Having the exact source context for type errors in compiled
code is also a big win:
    (defun test (x y)
      (if y (car x) (cdr x)))

    (test 'a t) ==>

    Type-error in TEST:
      A is not of type LIST

    (TEST A T) 

    6] source 1

    (IF Y (#:***HERE*** (CAR X)) (CDR X)) 

We're working on GUI's for source-level debugging, and already have an "edit
this error" capability.

>
>This is key; users should not need to disable checking except in
>small, well-tested parts of the code.

Yes, I agree totally.  With Python, we haven't seen any cases where the penalty
for safety is greater than 2x, and often it is 10-20%.

>The vast majority of
>code just isn't executed frequently enough to benefit, and the
>risk of turning off on large bodies of code is just too high
>to ever be worth it.

Python also provides a "weakened type checking" level for intermediate levels
of safety.  In this mode, type checks are weakened to some convenient
supertype.  MY-STRUCTURE becomes STRUCTURE, (INTEGER 3 7) becomes FIXNUM, etc.
There is also a "context-sensitive declaration" mechanism, which allows local
variations in safety, such as fully checking the types of arguments to external
functions, but having less safe internals.

  Robert MacLachlan
From: Bob Kerns
Subject: Re: CL type support (was Re: declare)
Date: 
Message-ID: <1991Aug29.012332.27352@crl.dec.com>
In article <·······················@cs.cmu.edu>, ····@cs.cmu.edu (Rob MacLachlan) writes:
> >I think you're expecting the compiler to do type-inferencing
> >to deduce that only one of the typecase clauses are needed.
> >However, in the practical world of existing compilers, it
> >isn't going to help you, because they won't.
> >
> >Too bad, because this is the cleanest technique.

I should clarify my overstatement:  It isn't going to help you
if your goal is portable code.  (Which it usually should be,
but there are exceptions).  You don't get much cleanliness
by using techniques that you then have to special-case for
other systems anyway.

> Hmmn, I thought Lucid might handle this easy case.

I just checked it again, and you're right; it does.  I must have
blown my earlier test somehow.  But there are enough others
out there that don't, that it forms the basis of my statement.

I'm glad to see some lisps DO handle it, though.

> Python also provides a "weakened type checking" level for intermediate levels
> of safety.  In this mode, type checks are weakened to some convenient
> supertype.  MY-STRUCTURE becomes STRUCTURE, (INTEGER 3 7) becomes FIXNUM, etc.
> There is also a "context-sensitive declaration" mechanism, which allows local
> variations in safety, such as fully checking the types of arguments to external
> functions, but having less safe internals.

This sounds like great stuff!