Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

portable way of strict type checking in defun

18 views
Skip to first unread message

alex_sv

unread,
Oct 12, 2009, 6:24:36 AM10/12/09
to
Hi all,

could you please clarify - is there a portable way of introducing
strict typechecking in lisp code?

I've found that "declare" statement makes it possible, but in sbcl and
not in clisp:

================================
sbcl:

* (defun foo (a b) (declare (fixnum a) (symbol b)) (list a b))

FOO
* (foo 1 2)

debugger invoked on a TYPE-ERROR: The value 2 is not of type SYMBOL.

Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.

(FOO 1 2)[:EXTERNAL]
0] 0

* (foo 1 'a)

(1 A)
*

===========================================
clisp:

[1]> (defun foo (a b) (declare (fixnum a) (symbol b)) (list a b))
foo
[2]> (foo 1 32)
(1 32)

- there is no typechecking in clisp - it expected to raise an error
here.

Tamas K Papp

unread,
Oct 12, 2009, 6:33:01 AM10/12/09
to
On Mon, 12 Oct 2009 03:24:36 -0700, alex_sv wrote:

> Hi all,
>
> could you please clarify - is there a portable way of introducing
> strict typechecking in lisp code?

Yes, see check-type (which uses typep).

> I've found that "declare" statement makes it possible, but in sbcl and
> not in clisp:

Declarations can be ignored. Checking them is a nice feature of SBCL
(but if I remember correctly, this may not happen, depending on your
safety settings), but don't rely on it for portability. In general, I
would use declarations for efficiency and check-type, etypecase etc
for typechecking.

HTH,

Tamas

Rainer Joswig

unread,
Oct 12, 2009, 6:35:24 AM10/12/09
to

The portable equivalent of what you show above can be done with CHECK-
TYPE (and optionally ASSERT).

(defun foo (a b)
(check-type a fixnum)
(check-type b symbol)
(list a b))

(foo 1 32)

Error: The value 32 of B is not of type SYMBOL.


Regards,

Rainer Joswig

Pascal J. Bourguignon

unread,
Oct 12, 2009, 6:38:18 AM10/12/09
to
alex_sv <avsha...@gmail.com> writes:

> Hi all,
>
> could you please clarify - is there a portable way of introducing
> strict typechecking in lisp code?
>
> I've found that "declare" statement makes it possible, but in sbcl and
> not in clisp:

Yes, DECLARE doesn't _enforce_ strict type checking,
it just declares the types, as a hint.
It is CHECK-TYPE that enforces strict type checking.


(defun foo (a b)
(check-type a fixnum)
(check-type b symbol)
(list a b))


C/USER[329]> (foo 1 32)

*** - The value of b should be of type symbol.
The value is: 32


--
__Pascal Bourguignon__

Rob Warnock

unread,
Oct 12, 2009, 6:55:27 AM10/12/09
to
alex_sv <avsha...@gmail.com> wrote:
+---------------

| could you please clarify - is there a portable way of introducing
| strict typechecking in lisp code?
+---------------

Yes, CHECK-TYPE:

http://www.lispworks.com/documentation/HyperSpec/Body/m_check_.htm
Macro CHECK-TYPE
Syntax:
check-type place typespec [string] => nil
...
CHECK-TYPE signals a correctable error of type TYPE-ERROR if the
contents of PLACE are not of the type TYPESPEC.

+---------------
| I've found that "declare" statement makes it possible...
+---------------

No it doesn't!! In Common Lisp, type declarations do *NOT* cause the
CL system to *check* types; rather, they're an assertion by you *TO*
the system that it can *trust* that the types are as you have declared
them. And if you violate that trust, "the consequences are undefined":

http://www.lispworks.com/documentation/HyperSpec/Body/03_c.htm
3.3 Declarations
...
The consequences are undefined if a program violates a declaration
or a proclamation.

http://www.lispworks.com/documentation/HyperSpec/Body/s_the.htm
...
THE specifies that the values[1a] returned by FORM are of the types
specified by VALUE-TYPE. The consequences are undefined if any result
is not of the declared type.

And in CL, "undefined" is the worst situation one can possibly be in:

http://www.lispworks.com/documentation/HyperSpec/Body/01_db.htm
...
The consequences are undefined

This means that the consequences are unpredictable. The consequences
may range from harmless to fatal. No conforming code may depend on
the results or effects. Conforming code must treat the consequences
as unpredictable. In places where the words ``must,'' ``must not,''
or ``may not'' are used, then ``the consequences are undefined''
if the stated requirement is not met and no specific consequence
is explicitly stated. An implementation is permitted to signal an
error in this case.

Note that while an "implementation is *permitted* to signal an error in
this case" [emphasis added], as you pointed out that SBCL does in *some*
cases of violations of type declarations (as do some other implementations,
in some cases, by the way), it is not required to.

So if you tell CL that FOO is an (INTEGER 17 53), you'd better make
sure that it is!! [See CHECK-TYPE, above...]


-Rob

-----
Rob Warnock <rp...@rpw3.org>
627 26th Avenue <URL:http://rpw3.org/>
San Mateo, CA 94403 (650)572-2607

Tamas K Papp

unread,
Oct 12, 2009, 7:20:04 AM10/12/09
to
On Mon, 12 Oct 2009 05:55:27 -0500, Rob Warnock wrote:

> Note that while an "implementation is *permitted* to signal an error in
> this case" [emphasis added], as you pointed out that SBCL does in *some*
> cases of violations of type declarations (as do some other
> implementations, in some cases, by the way), it is not required to.
>
> So if you tell CL that FOO is an (INTEGER 17 53), you'd better make sure
> that it is!! [See CHECK-TYPE, above...]

A related question that I have been wondering about: is the
implementation allowed to treat the check-type as a declaration of
that given type? If my understanding is correct, then check-type
cannot proceed without its argument being the appropriate type, so a
compiler could infer the type automatically.

But of course multithreaded environments can lead to complications.
Consider

(defun positive-vector-p (x)
(every #'plusp x))

(deftype positive-vector ()
`(and (array * (*))
(satisfies positive-vector-p)))

(defun foo (x)
(check-type x positive-vector)
(map 'vector #'/ x))

(foo #(1 2 3)) ; works
(foo #(0 2 3)) ; signals type error

But in another thread, elements of #(1 2 3) could be set to 0 _after_
the check-type, so an implementation assuming that a check-type
implies a declaration would be making a mistake. So I guess it is
better if the programmer needs to promise these things explicitly.

Are there simple examples not relying on threads?

Thanks,

Tamas

Tobias C. Rittweiler

unread,
Oct 12, 2009, 8:04:25 AM10/12/09
to
Tamas K Papp <tkp...@gmail.com> writes:

> A related question that I have been wondering about: is the
> implementation allowed to treat the check-type as a declaration of
> that given type?

Compilers can be sure that after a CHECK-TYPE the value in the specified
place is of the specified type, and can optimize accordingly. It is,
however, not the same as a declaration.

In case of

(defun foo1 (x)
(declare (string x))
...)

FOO1 is a function which takes a string as first parameter, whereas in

(defun foo2 (x)
(check-type x string)
...)

FOO2 is a function which takes an arbitrary value (type T) as first
parameter.

That is (FOO1 42) results in undefined behaviour, whereas (FOO2 42)
results in defined behaviour (run-time error)

HTH,

-T.

Giovanni Gigante

unread,
Oct 12, 2009, 10:17:14 AM10/12/09
to

> And in CL, "undefined" is the worst situation one can possibly be in:
>
> This means that the consequences are unpredictable. The consequences
> may range from harmless to fatal.


...Not just in CL. Just read from Associated Press:

"Sarkozy warned of unspecified consequences for the Czech Republic if
Klaus continues to withhold his signature."

:-)

alex_sv

unread,
Oct 12, 2009, 10:49:58 AM10/12/09
to
On 12 окт, 18:17, Giovanni Gigante <g...@cidoc.iuav.it> wrote:
> ...

OK, thank you all!

Finally I come to conclusion that it would be better to have some kind
of (declare-safe ...) macro that unrolled to just the same declare
call for sbcl and in series of (check-type...) calls in case of clisp
(or whatever lisp machine that does not support declare as sbcl does).

I believe that declare is more optimal in case of sbcl - it looks like
the compiler generates more optimal code for functions with declare
statement inside.

Tamas K Papp

unread,
Oct 12, 2009, 11:05:59 AM10/12/09
to
On Mon, 12 Oct 2009 07:49:58 -0700, alex_sv wrote:

> On 12 окт, 18:17, Giovanni Gigante <g...@cidoc.iuav.it> wrote:
>> ...
>
> OK, thank you all!
>
> Finally I come to conclusion that it would be better to have some kind
> of (declare-safe ...) macro that unrolled to just the same declare call
> for sbcl and in series of (check-type...) calls in case of clisp (or
> whatever lisp machine that does not support declare as sbcl does).

I don't know how you came to that conclusion. Please read the replies
that you got, and understand that check-type and declare are for two
different things. Also, the whole thing would be very fragile,
especially wrt your optimization settings.

Just to reiterate:

- declare means that _you_ promise something to the compiler,
- check-type makes the function check that.

If you want safe & micro-optimized code, use both. If you want to
skip the type checks because you are sure, use only declare.

> I believe that declare is more optimal in case of sbcl - it looks like
> the compiler generates more optimal code for functions with declare
> statement inside.

Can you back that up with an example? Omitting check-type will just
generate code without type checking -- what is optimal depends on your
requirements.

Also, the micro-optimizations you can get with declarations should be
done when you need them, not all the time, while checking types may be
a good thing more generally.

Tamas

Adam Michalik

unread,
Oct 12, 2009, 11:19:54 AM10/12/09
to dod...@gmail.com
alex_sv <avsha...@gmail.com> writes:

> Finally I come to conclusion that it would be better to have some kind
> of (declare-safe ...) macro that unrolled to just the same declare
> call for sbcl and in series of (check-type...) calls in case of clisp
> (or whatever lisp machine that does not support declare as sbcl does).

Come to think of it, how do you intend do implement it?

Let's suppose we have a macro DECLARE-TYPE which, used as in

(defun foo (a b)
(declare-type (number a) (symbol b))
(cons b a))

expands to

(defun foo (a b)
(declare (type number a)
(type symbol b))
(check-type a number)
(check-type b symbol)
(cons b a))

Clearly it needs to return more then one form, yet macros define by
defmacro are supposed to return single form. I'm missing solution.

--
Adam Michalik
vel Dodek Dodecki
<dodek[]dodecki.net>

Tobias C. Rittweiler

unread,
Oct 12, 2009, 11:31:42 AM10/12/09
to
Adam Michalik <dod...@gmail.com> writes:

Unfortunately, declarations are specially parsed by the forms that allow
them. The parsing does not involve macro expansion. I.e.: macros cannot
expand into declarations. See the CLHS entry for DECLARE.

-T.

Adam Michalik

unread,
Oct 12, 2009, 11:51:52 AM10/12/09
to dod...@gmail.com
"Tobias C. Rittweiler" <t...@freebits.de.invalid> writes:

> Unfortunately, declarations are specially parsed by the forms that allow
> them. The parsing does not involve macro expansion. I.e.: macros cannot
> expand into declarations. See the CLHS entry for DECLARE.

Clearly that's one of the few things macros cannot do. Thank you for
information.

But still, I wonder if it's possible for macro to expand into more than
one forms. So far, I've been using PROGN when I needed it.

Tamas K Papp

unread,
Oct 12, 2009, 12:06:16 PM10/12/09
to
On Mon, 12 Oct 2009 17:51:52 +0200, Adam Michalik wrote:

> "Tobias C. Rittweiler" <t...@freebits.de.invalid> writes:
>
>> Unfortunately, declarations are specially parsed by the forms that
>> allow them. The parsing does not involve macro expansion. I.e.: macros
>> cannot expand into declarations. See the CLHS entry for DECLARE.
>
> Clearly that's one of the few things macros cannot do. Thank you for
> information.
>
> But still, I wonder if it's possible for macro to expand into more than
> one forms. So far, I've been using PROGN when I needed it.

I guess you could write a codewalker and use defun*, but I don't really
see the point of the OP's suggestion, and I could always work around
things like this so far.

BTW, PROGN doesn't accept a DECLARE, but LOCALLY does, so I usually
use that.

Tamas

Pascal J. Bourguignon

unread,
Oct 12, 2009, 1:20:02 PM10/12/09
to
Adam Michalik <dod...@gmail.com> writes:

> alex_sv <avsha...@gmail.com> writes:
>
>> Finally I come to conclusion that it would be better to have some kind
>> of (declare-safe ...) macro that unrolled to just the same declare
>> call for sbcl and in series of (check-type...) calls in case of clisp
>> (or whatever lisp machine that does not support declare as sbcl does).
>
> Come to think of it, how do you intend do implement it?
>
> Let's suppose we have a macro DECLARE-TYPE which, used as in
>
> (defun foo (a b)
> (declare-type (number a) (symbol b))
> (cons b a))
>
> expands to
>
> (defun foo (a b)
> (declare (type number a)
> (type symbol b))

Good, so A is a NUMBER and B is a SYMBOL.

> (check-type a number)

Well, I know that A is a NUMBER, so I can skip this test. NOP.

> (check-type b symbol)

Well, I know that B is a SYMBOL, so I can skip this test. NOP.

> (cons b a))
>
> Clearly it needs to return more then one form, yet macros define by
> defmacro are supposed to return single form. I'm missing solution.

Once you have a declaration, it is useless to add a check-type, since
the declaration says that there's no way the type of the value may be
something else.

If you want to mix both, you have to use check-type first. The
post-condition of check-type is that the value is of the given type,
so you can declare it, (but any good compiler would infer it itself).


(defmacro with (type-declarations &body body)
`(progn
,@(mapcan (lambda (type-declaration)
(destructuring-bind (type &rest variables) type-declaration
(mapcar (lambda (variable)
`(check-type ,variable ,type))
variables)))
type-declarations)
(locally ,@(mapcar (lambda (type-declaration)
(destructuring-bind (type &rest variables) type-declaration
`(declare (type ,type ,@variables))))
type-declarations)
,@body)))


C/USER[340]> (macroexpand '(with ((number a)
(symbol b))
(cons a b)))
(PROGN
(CHECK-TYPE A NUMBER)
(CHECK-TYPE B SYMBOL)
(LOCALLY
(DECLARE (TYPE NUMBER A))
(DECLARE (TYPE SYMBOL B))
(CONS A B))) ;
T
C/USER[341]>

--
__Pascal Bourguignon__

Pascal J. Bourguignon

unread,
Oct 12, 2009, 1:20:50 PM10/12/09
to
"Tobias C. Rittweiler" <t...@freebits.de.invalid> writes:

Well, then I've done something that's impossible in my answer to Adam...

Hint: LOCALLY.

--
__Pascal Bourguignon__

Duane Rettig

unread,
Oct 12, 2009, 1:20:58 PM10/12/09
to
On Oct 12, 3:55 am, r...@rpw3.org (Rob Warnock) wrote:

[Rob, I'm answering your post for no particular reason; I could have
answered any others which gave the same general response to the OP.]

> alex_sv <avshaba...@gmail.com> wrote:
>
> +---------------
> | could you please clarify - is there a portable way of introducing
> | strict typechecking in lisp code?
> +---------------
>
> Yes, CHECK-TYPE:

...

> +---------------
> | I've found that "declare" statement makes it possible...
> +---------------
>
> No it doesn't!!

We have to be careful when we say "no" to someone about the
possibility of portability. When we do that for the reasons you list,
we set ourselves up for ridicule by the Lisp naysayers about why e.g.
there is no portable way to call a foreign function in Common Lisp.
The problem is in the definition of "portable" - it means different
things at different times. Perhaps we should invent a secret code
whereby we say "Portable" (capitalized) in the sense that Conforming
Programs are Portable, but "portable" in the more generic sense that
makes it possible for a program to not conform and yet run in several
different CL implementations. So for the example of foreign calls,
there is no Portable way to call a foreign function, but the concept
of ffi is portable and portable programs can be written in CL which
have calls to foreign functions.

In Common Lisp, type declarations do *NOT* cause the
> CL system to *check* types; rather, they're an assertion by you *TO*
> the system that it can *trust* that the types are as you have declared
> them. And if you violate that trust, "the consequences are undefined":

But the point of that last phrase is precisely that it is "possible"
for an implementation to define those undefined consequences in a way
similar to other implementations.

...

> Note that while an "implementation is *permitted* to signal an error in
> this case" [emphasis added], as you pointed out that SBCL does in *some*
> cases of violations of type declarations (as do some other implementations,
> in some cases, by the way), it is not required to.
>
> So if you tell CL that FOO is an (INTEGER 17 53), you'd better make
> sure that it is!!  [See CHECK-TYPE, above...]

Again, this advice conflates "Portability" with the possibility of
"portability".

You might say "but sbcl is the only implementation which does this,
and so such an expectation would not even be 'portable' (in the
generic sense)". Well, that's why I'm replying here; Allegro CL 8.2
will be joining sbcl in defining functionality for checking
declarations under certain optimization settings. As you might already
know, Allegro CL uses a secondary staging technique for tying
optimization levels to behaviors, which we call "compiler switches".
These are functions of 4 arguments (5 arguments in 8.2, since we added
compilation-speed) which return a boolean based on the current
settings. In 8.2, we've introduced a new switch called comp:verify-
type-declarations-switch, which when true causes the compiler to
generate the equivalent of check-type forms at let binding and setq
points in the code, whenever there is a type declaration for the
variable. The switch can be given any functionality desired, but the
default switch returns true when safety is greater than 1. This,
coupled with the already present comp:trust-declarations-switch (which
returns true when speed is greater than safety) allows several
combinations of compilation: at speed 1 and safety 2 declared types
are checked _and_ functionality is generic; at speed 3 and safety 1
code is generated full-bore trusting declarations completely, and the
new optimization space is when speed is 3 and safety is 2, where code
is generated with types being trusted, but also variables are checked
for the proper type before that trusting code is entered.

Why did we do this, when check-type has always been available? Well,
yes, assert and check-type have always been available to users who
want to seriously check their code at development time. However,
turning that code into production code involves painful editing of
source code, resulting in the possibility of introducing new bugs into
the code (due to typos and other edit errors). We have found that
more often, our customers will simply not use these standard tools and
either wing it or else create their own "maybe-assert" or "debug-check-
type" macros which has a switch that can be turned off without
touching the source code. It is for those people that we have added
this capability to Allegro CL.

I just hope you won't be too harsh on the OP who simply wants to be
able to have his implementation check his types for him simply and
cleanly. And yes, portability (with a small "p") is possible.

Duane

Pascal J. Bourguignon

unread,
Oct 12, 2009, 1:21:41 PM10/12/09
to
Adam Michalik <dod...@gmail.com> writes:

> "Tobias C. Rittweiler" <t...@freebits.de.invalid> writes:
>
>> Unfortunately, declarations are specially parsed by the forms that allow
>> them. The parsing does not involve macro expansion. I.e.: macros cannot
>> expand into declarations. See the CLHS entry for DECLARE.
>
> Clearly that's one of the few things macros cannot do. Thank you for
> information.

Do not believe all you read on the Internet.


> But still, I wonder if it's possible for macro to expand into more than
> one forms.

No, this is impossible.


> So far, I've been using PROGN when I needed it.

Or LOCALLY if you want to generate declarations.

--
__Pascal Bourguignon__

Duane Rettig

unread,
Oct 12, 2009, 1:28:02 PM10/12/09
to
On Oct 12, 8:31 am, "Tobias C. Rittweiler" <t...@freebits.de.invalid>
wrote:
> Adam Michalik <dode...@gmail.com> writes:

That's right; one would have to use Environments to do what is
desired, using define-declaration to create a new declaration type and
to define semantics for it. Another solution that is definitely not
Portable, but which could possibly be portable...

Duane


Tobias C. Rittweiler

unread,
Oct 12, 2009, 2:34:03 PM10/12/09
to
p...@informatimago.com (Pascal J. Bourguignon) writes:

> Adam Michalik <dod...@gmail.com> writes:
>
> > "Tobias C. Rittweiler" <t...@freebits.de.invalid> writes:
> >
> > > Unfortunately, declarations are specially parsed by the forms that allow
> > > them. The parsing does not involve macro expansion. I.e.: macros cannot
> > > expand into declarations. See the CLHS entry for DECLARE.
> >
> > Clearly that's one of the few things macros cannot do. Thank you for
> > information.
>
> Do not believe all you read on the Internet.

I agree.


> > But still, I wonder if it's possible for macro to expand into more than
> > one forms.
>
> No, this is impossible.
>
>
> > So far, I've been using PROGN when I needed it.
>
> Or LOCALLY if you want to generate declarations.

It's not the same thing. Declarations within LOCALLY are always free.

-T.

Tobias C. Rittweiler

unread,
Oct 12, 2009, 2:45:42 PM10/12/09
to
Duane Rettig <du...@franz.com> writes:

> You might say "but sbcl is the only implementation which does this,
> and so such an expectation would not even be 'portable' (in the
> generic sense)". Well, that's why I'm replying here; Allegro CL 8.2
> will be joining sbcl in defining functionality for checking
> declarations under certain optimization settings.

Bravo!


> ... at speed 1 and safety 2 declared types are checked _and_


> functionality is generic; at speed 3 and safety 1 code is generated
> full-bore trusting declarations completely, and the new optimization
> space is when speed is 3 and safety is 2, where code is generated with
> types being trusted, but also variables are checked for the proper
> type before that trusting code is entered.

What is the difference between (optimize (speed 3) (safety 0))
and (optimize (speed 3) (safety 1))?

Personally, I'm surprised that declarations are blindly trusted and not
checked for a safety level of 1.

-T.

Don Geddis

unread,
Oct 12, 2009, 3:04:40 PM10/12/09
to
alex_sv <avsha...@gmail.com> wrote on Mon, 12 Oct 2009:
> Finally I come to conclusion that it would be better to have some kind
> of (declare-safe ...) macro that unrolled to just the same declare
> call for sbcl and in series of (check-type...) calls in case of clisp
> (or whatever lisp machine that does not support declare as sbcl does).

Your conclusion is probably wrong, but in any case: why not just use
CHECK-TYPE everywhere, including in SBCL?

> I believe that declare is more optimal in case of sbcl - it looks like the
> compiler generates more optimal code for functions with declare statement
> inside.

You actually have an example, where code with DECLARE is "more optimal"
in SBCL than code with CHECK-TYPE?

Can you provide us the example?

-- Don
_______________________________________________________________________________
Don Geddis http://don.geddis.org/ d...@geddis.org
If they have a layoff at Victoria's Secret, do they give people pink slips?
-- Duff Howell

alex_sv

unread,
Oct 12, 2009, 4:18:23 PM10/12/09
to
On 12 окт, 23:04, Don Geddis <d...@geddis.org> wrote:
> alex_sv <avshaba...@gmail.com> wrote on Mon, 12 Oct 2009:
Thank you all for your answers.

>
> > Finally I come to conclusion that it would be better to have some kind
> > of (declare-safe ...) macro that unrolled to just the same declare
> > call for sbcl and in series of (check-type...) calls in case of clisp
> > (or whatever lisp machine that does not support declare as sbcl does).

I thought I can do it using eval machinery, I mean make macro that
produces something like that:
(eval '(progn (check-type a fixnum) (check-type b symbol)))
...
But it looks like a big overkill. And finally this approach just does
not work - because eval does not "captures" arguments, I mean
the similar constructions won't work:
(defun foo (a) (eval '(+ 1000 a)))

>
> Your conclusion is probably wrong, but in any case: why not just use
> CHECK-TYPE everywhere, including in SBCL?

It looks good enough solution, though a bit verbose.
May be it's even better to throw away clisp and proceed with sbcl
only :)

>
> > I believe that declare is more optimal in case of sbcl - it looks like the
> > compiler generates more optimal code for functions with declare statement
> > inside.
>
> You actually have an example, where code with DECLARE is "more optimal"
> in SBCL than code with CHECK-TYPE?
>
> Can you provide us the example?

Well, I know that performance gain will be negligible in most cases.
But compiled versions of the function with declare statement has
shorter (and probably faster) code (I didn't attached it because it is
huge even for small functions, but you can easily check it).

E.g. compiled version of (defun foo (a b) (declare (fixnum a b)) (list
a b)) takes 41 assembly statements, compiled version of (defun foo (a
b) (check-type a fixnum) (check-type b fixnum) (list a b)) takes 86
assembly statements - though there are many NOPs there. These
measurements was made using sbcl 1.0.29 for linux-x86_64, without any
optimization turned on - I just entered function on just-launched sbcl
and invoked disassemble.

I didn't analyze the assembly because of lack of knowledges of x64
assembly, but shorter version made me believe that declare is worth
using on sbcl instead of more verbose check-type.
Of course I can't prove that there are no circumstances where check-
type results in leaner and faster code.

Thanks,
Alex

Tamas K Papp

unread,
Oct 12, 2009, 5:46:01 PM10/12/09
to
On Mon, 12 Oct 2009 13:18:23 -0700, alex_sv wrote:

Well, I know that performance gain will be negligible in most cases. But
> compiled versions of the function with declare statement has shorter
> (and probably faster) code (I didn't attached it because it is huge even
> for small functions, but you can easily check it).
>
> E.g. compiled version of (defun foo (a b) (declare (fixnum a b)) (list a
> b)) takes 41 assembly statements, compiled version of (defun foo (a b)
> (check-type a fixnum) (check-type b fixnum) (list a b)) takes 86
> assembly statements - though there are many NOPs there. These
> measurements was made using sbcl 1.0.29 for linux-x86_64, without any
> optimization turned on - I just entered function on just-launched sbcl
> and invoked disassemble.
>
> I didn't analyze the assembly because of lack of knowledges of x64
> assembly, but shorter version made me believe that declare is worth
> using on sbcl instead of more verbose check-type. Of course I can't
> prove that there are no circumstances where check- type results in
> leaner and faster code.

Gosh, do you really _care_? Is this the bottleneck in your
application? Are you sure that the time you are spending on this
could not be spent on improving the algorithm, extracting speed gains
an order of magnitude larger (than something you didn't even
benchmark)?

I think that there should be contrib modules for SBCL that make the
user attach electrodes to his/her body before using declarations.
Then, unless each minute spent writing the silly things and debugging
subtle bugs resulting from broken type promises and (safety 0),
results in a reduction of runtime speed by 1/N minutes (10=newbie
mode, 1=expert mode), a small electric shock would be applied.

Tamas

Rob Warnock

unread,
Oct 12, 2009, 8:27:03 PM10/12/09
to
Tobias C. Rittweiler <t...@freebits.de.invalid> wrote:
+---------------
+---------------

[I assume you're using "free" here in contrast to "bound", yes?]

True, and as noted previously, macros may not expand directly into
raw declarations [free *or* bound]. But macros *may* expand into
complete binding forms such as LET/LET*/FLET/LABELS/MACROLET/
SYMBOL-MACROLET/DEFUN/DEFVAR/etc. including bound declarations
*within* those forms. So the restriction is seldom a serious
impediment to coding.

Vassil Nikolov

unread,
Oct 12, 2009, 9:35:11 PM10/12/09
to

"Unspecified" is different from "undefined". The former does not
permit dragons to fly out of Brussels ^W the CPU's fan...

---Vassil.


--
"Even when the muse is posting on Usenet, Alexander Sergeevich?"

Vassil Nikolov

unread,
Oct 12, 2009, 9:52:17 PM10/12/09
to

On Mon, 12 Oct 2009 20:34:03 +0200, "Tobias C. Rittweiler" <t...@freebits.de.invalid> said:
> ...

> Declarations within LOCALLY are always free.

Which exactly are the cases when this matters, though? I may be
missing something obvious, but in the absence of special
declarations, is there a difference between

(let (<bindings>)
(declare . <declarations>)
. <body>)

and

(let (<bindings>)
(locally (declare . <declarations>)
. <body>))

where all variables appearing in the declarations appear in the
body?

Adam Michalik

unread,
Oct 13, 2009, 1:46:46 AM10/13/09
to dod...@gmail.com
Tamas K Papp <tkp...@gmail.com> writes:

> Gosh, do you really _care_? Is this the bottleneck in your
> application? Are you sure that the time you are spending on this
> could not be spent on improving the algorithm, extracting speed gains
> an order of magnitude larger (than something you didn't even
> benchmark)?

Frequently this's not possible, and when this's the case, the
only thing we can do is to decrease the constants.

Duane Rettig

unread,
Oct 13, 2009, 2:44:24 AM10/13/09
to
On Oct 12, 11:45 am, "Tobias C. Rittweiler" <t...@freebits.de.invalid>
wrote:

> Duane Rettig <du...@franz.com> writes:
> > You might say "but sbcl is the only implementation which does this,
> > and so such an expectation would not even be 'portable' (in the
> > generic sense)".  Well, that's why I'm replying here; Allegro CL 8.2
> > will be joining sbcl in defining functionality for checking
> > declarations under certain optimization settings.
>
> Bravo!

Thanks.

> > ... at speed 1 and safety 2 declared types are checked _and_
> > functionality is generic; at speed 3 and safety 1 code is generated
> > full-bore trusting declarations completely, and the new optimization
> > space is when speed is 3 and safety is 2, where code is generated with
> > types being trusted, but also variables are checked for the proper
> > type before that trusting code is entered.
>
> What is the difference between (optimize (speed 3) (safety 0))
> and (optimize (speed 3) (safety 1))?

In Allegro CL, the combination of speed 3 and safety 0 is relegated to
compilation techniques which might be more aggressive than should be
used casually. For example, the "fixnums-remain-fixnums" switch fires
at this setting, where fixnums are assumed to stay fixnum through +
and - operations. Another switch that _doesn't_ fire at this speed/
safety setting is the verify-argument-count switch, which checks for
the number of arguments coming into a function. All of the switches
that change at speed 3 and safety 0 are designed for speed, but are
dangerous for programs that are not fully debugged (and the fixnums-
remain-fixnum switch will actually generate incorrect code if fixnums
in fact should have overflowed to bignums. For those reasons, we
don't recommend a combination of speed 3 and safety 0 in general,
unless you _really_ need the speed, and you're willing to risk the
danger.

As for the speed 3 and safety 1 setting, it is a much more safe speedy
setting. But instead of me describing the operations it provides by
default, it is easy enough to find out what each switch is doing for
any particular optimization setting - first evaluate the (declaim
(optimize ...)) form, and then type (excl:explain-compiler-settings)
to see what switches are on and what ones are off. This should give
you an idea of what the compiler is doing at that optimization
setting.

> Personally, I'm surprised that declarations are blindly trusted and not
> checked for a safety level of 1.

Why do you say "blindly"? The trust-declarations-switch doesn't
remove all type checking or generic functionality. Instead, it allows
types to be used in compilation, and propagated through the forms, in
order to make good decisions about how to optimize the code. But
consider for example the verify-non-generic-switch; it will return
true (i.e. generate more stringent code) at speed 2 and safety 1, even
when trust-declarations-switch returns true at that speed/safety
setting.

On the other hand, our philosophy has always been to trust user's
declarations when they tell the compiler to do so - I always detest it
when a program thinks it is smarter than I am, and I won't use that
program for long. As for the new switch for which declarations are
checked, this still meets with our philosophy, where instead of
trusting declarations when the user tells it to, declarations can
actually be _tested_ when the user asks the compiler to do so.

Duane

Tobias C. Rittweiler

unread,
Oct 13, 2009, 3:54:06 AM10/13/09
to
Vassil Nikolov <vnik...@pobox.com> writes:

> On Mon, 12 Oct 2009 20:34:03 +0200, "Tobias C. Rittweiler" <t...@freebits.de.invalid> said:
> > ...
> > Declarations within LOCALLY are always free.
>
> Which exactly are the cases when this matters, though? I may be
> missing something obvious, but in the absence of special
> declarations, is there a difference between
>
> (let (<bindings>)
> (declare . <declarations>)
> . <body>)
>
> and
>
> (let (<bindings>)
> (locally (declare . <declarations>)
> . <body>))
>
> where all variables appearing in the declarations appear in the
> body?

Semantically, yes, it "only" makes a difference for the SPECIAL
declaration, in ANSI Common Lisp that is.

The point is that there _is_ a difference, and implementations are free
to extend on that (for example by providing a LEXICAL declaration, or a
CONSTANT declaration.)

There are also venues of optimizations that free type declarations won't
allow for. For example, if your implementations supports specialized
physical envs, it may be able to store the float unboxed in the closure
for something like

(let ((x 1.0d0))
(declare (double-float x))
#'(lambda (y)
(setf x (* x (the double-float y)))
x))

-T.

Tamas K Papp

unread,
Oct 13, 2009, 5:00:59 AM10/13/09
to
On Tue, 13 Oct 2009 07:46:46 +0200, Adam Michalik wrote:

> Tamas K Papp <tkp...@gmail.com> writes:
>
>> Gosh, do you really _care_? Is this the bottleneck in your
>> application? Are you sure that the time you are spending on this could
>> not be spent on improving the algorithm, extracting speed gains an
>> order of magnitude larger (than something you didn't even benchmark)?
>
> Frequently this's not possible, and when this's the case, the only thing
> we can do is to decrease the constants.

Sure, but I have seen no evidence that this is actually the case here.
FWTW, no benchmarks either. Just a statement that check-type produces
extra code, and a suggestion that this could make things "slower"
(which is not really sound, since that code could be dealing with what
to do in case of a type mismatch, which is skipped in the normal case
anyhow).

Micro-optimization without benchmarking and profiling is the most
idiotic thing one can do in any language, but in modern CL
implementations that is even more pronounced. Eg with SBCL, I find it
extremely hard to predict in advance where the inefficiencies will
come from, since I frequently find that operations I would think of as
"slow" were very heavily optimized exactly for that reason, and now
they are much faster.

Tamas


alex_sv

unread,
Oct 13, 2009, 5:27:43 AM10/13/09
to
On 13 окт, 13:00, Tamas K Papp <tkp...@gmail.com> wrote:
> On Tue, 13 Oct 2009 07:46:46 +0200, Adam Michalik wrote:
> > Tamas K Papp <tkp...@gmail.com> writes:
>
> >> Gosh, do you really _care_?

No I really don't. That's just an observation.

> Micro-optimization without benchmarking and profiling is the most

> idiotic thing one can do in any language...

I am not going to rely on such a micro optimizations.
I know that "premature optimization is a root of all evil" :)
In fact such a constraints aren't needed too much due to dynamic lisp
nature.

Thomas F. Burdick

unread,
Oct 13, 2009, 7:05:58 AM10/13/09
to
On Oct 12, 7:20 pm, Duane Rettig <du...@franz.com> wrote:

> In 8.2, we've introduced a new switch called comp:verify-
> type-declarations-switch, which when true causes the compiler to
> generate the equivalent of check-type forms at let binding and setq
> points in the code, whenever there is a type declaration for the
> variable.  The switch can be given any functionality desired, but the
> default switch returns true when safety is greater than 1.  This,
> coupled with the already present comp:trust-declarations-switch (which
> returns true when speed is greater than safety) allows several
> combinations of compilation: at speed 1 and safety 2 declared types
> are checked _and_ functionality is generic; at speed 3 and safety 1
> code is generated full-bore trusting declarations completely, and the
> new optimization space is when speed is 3 and safety is 2, where code
> is generated with types being trusted, but also variables are checked
> for the proper type before that trusting code is entered.

Declarations as assertions in Allegro, you just made my week Duane!

Don Geddis

unread,
Oct 13, 2009, 11:50:20 PM10/13/09
to
alex_sv <avsha...@gmail.com> wrote on Mon, 12 Oct 2009:
> I thought I can do it using eval machinery, I mean make macro that
> produces something like that:
> (eval '(progn (check-type a fixnum) (check-type b symbol)))

If you want to make a macro, then why add the EVAL? Just make a macro
that returns the PROGN form you wrote above, and it should work just fine.
No need for any EVAL.

>> > I believe that declare is more optimal in case of sbcl - it looks like the
>> > compiler generates more optimal code for functions with declare statement
>> > inside.
>>

> But compiled versions of the function with declare statement has
> shorter

[...]


> E.g. compiled version of (defun foo (a b) (declare (fixnum a b)) (list
> a b)) takes 41 assembly statements, compiled version of (defun foo (a
> b) (check-type a fixnum) (check-type b fixnum) (list a b)) takes 86
> assembly statements - though there are many NOPs there. These
> measurements was made using sbcl 1.0.29 for linux-x86_64, without any
> optimization turned on - I just entered function on just-launched sbcl
> and invoked disassemble.

Have you considered that maybe the assembly code with CHECK-TYPE is actually
checking the types of the arguments? And maybe the assembly code from the
DECLARE form is missing those type checks?

You claimed, in your original post, that you wanted each function to check
the types of the arguments you pass in.

If the DECLARE version doesn't do that, then even if it's shorter, it isn't
really a solution to your problem, is it?

> I didn't analyze the assembly because of lack of knowledges of x64
> assembly, but shorter version made me believe that declare is worth
> using on sbcl instead of more verbose check-type.

Perhaps the algorithms aren't the same.

-- Don
_______________________________________________________________________________
Don Geddis http://don.geddis.org/ d...@geddis.org

Well done. You'll make an exemplary FOOD ANIMAL.

Vassil Nikolov

unread,
Oct 14, 2009, 2:51:57 AM10/14/09
to

On Tue, 13 Oct 2009 09:54:06 +0200, "Tobias C. Rittweiler" <t...@freebits.de.invalid> said:
> ...
> There are also venues of optimizations that free type declarations won't
> allow for. For example, if your implementations supports specialized
> physical envs, it may be able to store the float unboxed in the closure
> for something like

> (let ((x 1.0d0))
> (declare (double-float x))
> #'(lambda (y)
> (setf x (* x (the double-float y)))
> x))

Yes, that is what I missed (or at least one thing I missed; thinking
only of what is in the standard), thanks.

Tobias C. Rittweiler

unread,
Oct 14, 2009, 12:02:09 PM10/14/09
to
Don Geddis <d...@geddis.org> writes:

> Have you considered that maybe the assembly code with CHECK-TYPE is actually
> checking the types of the arguments? And maybe the assembly code from the
> DECLARE form is missing those type checks?

In default optimization settings, declarations are interpreted as
assertions in SBCL, and type checks are produced.

CHECK-TYPE, however, does a little more than type checking. That's the
reason behind the few additional instructions the OP sees.

-T.

Juanjo

unread,
Oct 14, 2009, 12:41:34 PM10/14/09
to
On Oct 14, 6:02 pm, "Tobias C. Rittweiler" <t...@freebits.de.invalid>
wrote:

> Don Geddis <d...@geddis.org> writes:
> > Have you considered that maybe the assembly code with CHECK-TYPE is actually
> > checking the types of the arguments?  And maybe the assembly code from the
> > DECLARE form is missing those type checks?
>
> In default optimization settings, declarations are interpreted as
> assertions in SBCL, and type checks are produced.

ECL also adopted this behavior not too long ago.

Juanjo

Tobias C. Rittweiler

unread,
Oct 14, 2009, 1:19:08 PM10/14/09
to
Juanjo <...> writes:

> On Oct 14, 6:02�pm, "Tobias C. Rittweiler" wrote:
>
> > Don Geddis <d...@geddis.org> writes:
> > > Have you considered that maybe the assembly code with CHECK-TYPE is actually
> > > checking the types of the arguments? �And maybe the assembly code from the
> > > DECLARE form is missing those type checks?
> >
> > In default optimization settings, declarations are interpreted as
> > assertions in SBCL, and type checks are produced.
>
> ECL also adopted this behavior not too long ago.

Bravo to you, too!

-T.

PS.

ABCL does, too, to some extent. And I'm told that Peter Graves' XCL
does, too.

Duane Rettig

unread,
Oct 14, 2009, 4:57:55 PM10/14/09
to
On Oct 14, 9:02 am, "Tobias C. Rittweiler" <t...@freebits.de.invalid>
wrote:

> Don Geddis <d...@geddis.org> writes:
> > Have you considered that maybe the assembly code with CHECK-TYPE is actually
> > checking the types of the arguments?  And maybe the assembly code from the
> > DECLARE form is missing those type checks?
>
> In default optimization settings, declarations are interpreted as
> assertions in SBCL, and type checks are produced.

Ahh, this is the reason why you asked me about Allegro CL "blindly
trusting" declarations earlier; we only generate the extra checks when
asked for them, rather than at the default settings. The philosophy
behind this is precisely due to our leanings toward declarations as
promises, and not checks. Also, under default settings generic (not
in the CLOS sense) code is run, which dispatches based on argument
types and which errors when necessary. So it is not blind trust
anyway; the errors simply occur at a different point because the
checks aren't redundantly done at every point in the code where things
might go wrong. Again, this is just a difference in philosophy about
what a declaration actually _is_.

Duane

Raymond Toy

unread,
Oct 14, 2009, 6:03:37 PM10/14/09
to Duane Rettig
Duane Rettig wrote:
> On Oct 12, 3:55 am, r...@rpw3.org (Rob Warnock) wrote:
>> So if you tell CL that FOO is an (INTEGER 17 53), you'd better make
>> sure that it is!! [See CHECK-TYPE, above...]
>
> Again, this advice conflates "Portability" with the possibility of
> "portability".
>
> You might say "but sbcl is the only implementation which does this,
> and so such an expectation would not even be 'portable' (in the
> generic sense)". Well, that's why I'm replying here; Allegro CL 8.2

SBCL is not the only implementation which does this. Rob knows that
too. :-)

Ray

Tobias C. Rittweiler

unread,
Oct 14, 2009, 6:24:16 PM10/14/09
to
Duane Rettig <du...@franz.com> writes:

> So it is not blind trust anyway; the errors simply occur at a
> different point because the checks aren't redundantly done at every
> point in the code where things might go wrong.

OTOH, the advantage of failing early is, of course, that it'll show the
reason not the symptom of a problem.

I had the impression that ACL's compiler could manage multiple entry
points into functions, so couldn't it generate calls which skip repeated
type checks? Ah, well, type checks probably have to come after argument
parsing, so that might be rather tricky.. mhm.


-T.


Don Geddis

unread,
Oct 14, 2009, 10:53:30 PM10/14/09
to
"Tobias C. Rittweiler" <t...@freebits.de.invalid> wrote on Wed, 14 Oct 2009:
> CHECK-TYPE, however, does a little more than type checking.

What else does it do?

Tobias C. Rittweiler

unread,
Oct 15, 2009, 3:27:50 AM10/15/09
to
Don Geddis <d...@geddis.org> writes:

> "Tobias C. Rittweiler" <t...@freebits.de.invalid> wrote on Wed, 14 Oct 2009:
> > CHECK-TYPE, however, does a little more than type checking.
>
> What else does it do?

CHECK-TYPE establishes a loop around a STORE-VALUE restart to repeatedly
ask the user for corrected values.

-T.

Rob Warnock

unread,
Oct 15, 2009, 5:20:25 AM10/15/09
to
Raymond Toy <rt...@earthlink.net> wrote:
+---------------
+---------------

Indeed, in an earlier reply I *almost* said "so does CMUCL", but
I've previously been chided for comparing CMUCL & SBCL too closely ;-}
so I just said "(as do some other implementations, in some cases,
by the way)".

0 new messages