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

Yet another syntactic sugar of mine

12 views
Skip to first unread message

budden

unread,
Dec 27, 2009, 10:25:15 AM12/27/09
to
Hi List!

http://paste.lisp.org/display/92680

? (macroexpand-1 '(with-conc-name x symbol x.name))
(PROGN (SYMBOL-NAME X))

? (defstruct long-structure-name fld1 fld2)
? (macroexpand-1
'(with-conc-name
x long-structure-name (list x.fld1 x.fld2)))
(PROGN (LIST
(VERY-LONG-STRUCTURE-TYPE-NAME-FLD1 X)
(VERY-LONG-STRUCTURE-TYPE-NAME-FLD2 X)))

We really don't need to know accessor names to
make this with- construct. Just look what user would
enter and only then check if there exist appropriate accessor
or not.

I seldom use CLOS, but I think two extensions
to with-conc-name might be useful. I might want to
transform x->foo to (foo-of x) [Thanks, Lars],
and maybe x-->foo to (slot-value x 'foo)

Well, now CL is about to approaching a state where I can really
start enjoy writing code in it and reading what I have written...

budden

unread,
Dec 27, 2009, 10:31:49 AM12/27/09
to
Oops, docs were wrong. See
http://paste.lisp.org/display/92680#1

Lars Rune Nøstdal

unread,
Dec 28, 2009, 4:34:49 AM12/28/09
to


Hm, sort of related I guess(?) -- I've been using this at times:

CL-USER> (set-macro-character #\¤
(lambda (stream char)
(declare (ignore char))
`(slot-value %with-object ',(read
stream)))
t)
T

CL-USER> (defmacro with-object (object &body body)
`(let ((%with-object ,object))
(declare (ignorable %with-object))
,@body))

WITH-OBJECT

CL-USER> (defclass universe () ((answer :initform 42)))
#<STANDARD-CLASS UNIVERSE>

CL-USER> (with-object (make-instance 'universe)
(format t "The Answer is ~A.~%" ¤answer))
The Answer is 42.
NIL

CL-USER>

Lars Rune Nøstdal

unread,
Dec 28, 2009, 5:45:08 AM12/28/09
to

Or well, really using NAMED-READTABLES for the syntax stuff;
http://common-lisp.net/project/editor-hints/darcs/named-readtables/doc/named-readtables.html

Lars Rune Nøstdal

unread,
Dec 28, 2009, 6:02:54 AM12/28/09
to
On Dec 28, 10:34 am, Lars Rune Nøstdal <larsnost...@gmail.com> wrote:


Here's some related hackery:


CL-USER> (defvar *with-object* nil)
*WITH-OBJECT*

CL-USER> (proclaim '(sb-ext:always-bound *with-object*)) ;; Slight
performance boost for SBCL.
; No value

CL-USER> (set-macro-character #\¤
(lambda (stream char)
(declare (ignore char))

`(slot-value *with-object* ',(read
stream)))
t)
T

CL-USER> (defclass self-ref () ())
#<STANDARD-CLASS SELF-REF>

CL-USER> (defmethod initialize-instance :around ((self-ref self-ref)
&key)
(let ((*with-object* self-ref))
(when (next-method-p)
(call-next-method))))
#<STANDARD-METHOD INITIALIZE-INSTANCE :AROUND (SELF-REF) {1003ED3D51}>

CL-USER> (defmacro with-object (object &body body)

`(let ((*with-object* ,object))
(declare (ignorable *with-object*))
,@body))
WITH-OBJECT

CL-USER> (defclass universe (self-ref)
((half-truth-a :initform (/ 42 2))

(half-truth-b :initform (/ 42 2))

(ultimate-truth :initform (+ ¤half-truth-a
¤half-truth-b))))
#<STANDARD-CLASS UNIVERSE>

CL-USER> (with-object (make-instance 'universe)
(values ¤half-truth-a
¤half-truth-b
¤ultimate-truth))
21
21
42

CL-USER>

budden

unread,
Dec 29, 2009, 5:23:18 AM12/29/09
to
Hi Lars!
I'll try to establish why my solution is better.
1. Readtables seem to increase complexity, if we're talking
about portable solution. SLIME can switch package-associated
readtable as you switch packages. I'm not sure Lispworks and
Allegro can do so.
2. At one time, you can refer to one object only. With my
macro, there is no such limitation. Also, you see, you're
not bound to objects at all:

(with-conc-name x symbol
(with-conc-name y universe
(setf y-->name x.name)))

Also, it might be reasonable to combine several things
and approach static typing. E.g.

(with-typed-object (x (make-instance 'universe) universe)
(x.some-method x-->half-truth-a))

Which would expand as

(let ((x (make-instance 'universe)))
(declare (type universe x))
(universe-some-method (slot-value x 'half-truth-a)))

The question is if mapping a code tree is a reliable
technique to find all instances of x.y symbols.
How would it interact with macroexpansion? It seems
all should be ok, but still I'm not sure.

Kalle Olavi Niemitalo

unread,
Dec 29, 2009, 6:51:00 AM12/29/09
to
budden <budde...@mail.ru> writes:

> ? (macroexpand-1 '(with-conc-name x symbol x.name))
> (PROGN (SYMBOL-NAME X))

I recall grepping for some symbols in CMUCL sources and not
finding their definitions anywhere. It turns out the definitions
were hidden in non-standard macros that concatenated strings.

Perhaps it just means I should have RTFM and then used some
CMUCL-specific cross-referencing feature instead of grep; but
still, I'd rather not use such macros, at least not for
definitions.

Pascal J. Bourguignon

unread,
Dec 29, 2009, 7:22:45 AM12/29/09
to
budden <budde...@mail.ru> writes:

(let ((x (make-instance 'cow)))
(x.run x-->size)))


--
__Pascal Bourguignon__ http://www.informatimago.com/

budden

unread,
Dec 29, 2009, 7:27:16 AM12/29/09
to
On 29 дек, 14:51, Kalle Olavi Niemitalo <k...@iki.fi> wrote:

> budden <budden-l...@mail.ru> writes:
> > ? (macroexpand-1 '(with-conc-name x symbol x.name))
> > (PROGN (SYMBOL-NAME X))
>
> I recall grepping for some symbols in CMUCL sources and not
> finding their definitions anywhere.  
Yes, several days ago I was looking for definition of
collect-garbase of something like this in SBCL. I was unable
to find it with grep. But it was rather easy to find out with slime's
M-.
that collect-garbase is a wrapper for collect_garbage written in C.

I think SBCL and CMUCL are examples of fairly big and
successful CL projects and if their authors allowed themselves
to use the technique, I can do that too.

> Perhaps it just means I should have RTFM and then used some
> CMUCL-specific cross-referencing feature instead of grep;

Certainly, cross-referencing is much more powerful and convinient
than grep in most (about 80%) cases. This is one of the great
advantages of CL. IMO it is a must to use if you want to be
productive. Happily, this is easy with SLIME.

> still, I'd rather not use such macros, at least not for
> definitions.

Well, I agree that, generally speaking, there is such
a problem. Thanks for noting it. But I suggest to use the macro
only in the body of function definition. I think this is appropriate.
Or do you mean that grep will be unable to find all uses of some
method? Yes, this is more serious problem. I'll think of it, thanks.

budden

unread,
Dec 29, 2009, 7:32:13 AM12/29/09
to
>(with-typed-object (x (make-instance 'universe) universe)
>  (let ((x (make-instance 'cow)))
>      (x.run x-->size)))
This is general problem of lexical binding, not
that of suggested macro. One can just write without
any macros:

(let ((x (make-instance 'universe)))
(let ((x (make-instance 'cow)))
(cow-run x (slot-value x 'size))))

Pascal J. Bourguignon

unread,
Dec 29, 2009, 11:44:15 AM12/29/09
to
budden <budde...@mail.ru> writes:

>>(with-typed-object (x (make-instance 'universe) universe)
>>� (let ((x (make-instance 'cow)))
>> � � �(x.run x-->size)))
> This is general problem of lexical binding, not
> that of suggested macro. One can just write without

> any macros[.]

Yes, but this is a case your code walker in your macro will have to
deal with.

budden

unread,
Dec 29, 2009, 1:03:32 PM12/29/09
to
> Yes, but this is a case your code walker in your macro will have to
> deal with.
I see. Well, for now I'd prefer just not to bind x inside
(with-typed-object (x ...))
I have already mentioned in a docs that I have no code walker, I just
replace symbols by their names. Strictly speaking, this is an
incorrect
behaviour, but I don't care... Documented bug is a feature :) It took
me
an hour to write and debue my macro, and I believe it is useful.
Writing a
code walker would take couple of days. Why bother myself with it? When
you
look at SLIME, there is rather similar with-struct*, it is
implemented
with macrolet and would suffer from the same problem. Then, why should
I care?

Well, ok, I mean patches are welcome :)

Rob Warnock

unread,
Dec 29, 2009, 10:33:03 PM12/29/09
to
Kalle Olavi Niemitalo <k...@iki.fi> wrote:
+---------------
+---------------

Having learned several such hacky frobnication techniques from looking
at the CMUCL sources, I confess I do tend to use them myself. ;-} ;-}
But you are quite correct that "grep" alone is not always sufficient
for finding stuff.

However, in CMUCL APROPOS and/or DESCRIBE can be very useful for
finding stuff, but *only* if you have the "target:" search list[1]
set correctly for your installation, which is normally done in
"$CMUCL_LIB/site-init.lisp" ["/usr/local/lib/cmucl/lib/site-init.lisp",
usually, but not always -- depends on how CMUCL was installed],
e.g., make sure that there's something like this in your "site-init":

(setf (search-list "target:") "/usr/local/src/cmucl/src/")

[Or wherever you want to put the sources -- they *don't* have to be
in the same tree as the main installation, they just have to match
what's running.]

For example, "grep -i 'defun.*dynamic-space-size' files..." won't
tell you where the function DYNAMIC-SPACE-SIZE is defined, but using
APROPOS & DESCRIBE you can get to the source file:

cmu> (apropos "DYNAMIC-SPACE-SIZE")

:DYNAMIC-SPACE-SIZE [constant] value: :DYNAMIC-SPACE-SIZE
LISP::DYNAMIC-SPACE-SIZE [function] ()
cmu> (describe 'LISP::DYNAMIC-SPACE-SIZE)

DYNAMIC-SPACE-SIZE is an internal symbol in the LISP package.
Function: #<Function LISP::DYNAMIC-SPACE-SIZE {105ACF61}>
Function arguments:
There are no arguments.
Its defined argument types are:
NIL
Its result type is:
(UNSIGNED-BYTE 32)
It is currently declared inline; expansion is available.
On Wednesday, 4/22/09 06:39:42 pm PDT it was compiled from:
target:code/gc.lisp
Created: Monday, 1/31/05 10:02:58 am PST
Comment: $Header: /project/cmucl/cvsroot/src/code/gc.lisp,v 1.42 2005-01-31 18:02:58 rtoy Exp $
cmu>

and from there you can look in "$CMUCL/src/code/gc.lisp" and find that
DYNAMIC-SPACE-SIZE was defined by a DEFUN that was generated by a call
to the C-VAR-FROB macro:

(eval-when (compile eval)
(defmacro c-var-frob (lisp-fun c-var-name)
`(progn
(declaim (inline ,lisp-fun))
(defun ,lisp-fun ()
(alien:extern-alien ,c-var-name (alien:unsigned 32))))))
...
(c-var-frob dynamic-space-size "dynamic_space_size")
...


-Rob

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

Kalle Olavi Niemitalo

unread,
Dec 31, 2009, 6:19:33 PM12/31/09
to
rp...@rpw3.org (Rob Warnock) writes:

> However, in CMUCL APROPOS and/or DESCRIBE can be very useful for

> finding stuff, [...]

DESCRIBE will indeed name the source file of a function or macro:

| * (describe 'x86::defenum)
|
| DEFENUM is an internal symbol in the X86 package.
| Macro-function: #<Function (:MACRO X86::DEFENUM) {10D507B9}>
| Macro arguments:
| ((&key (prefix "") (suffix "") (start 0) (step 1)) &rest identifiers)
| On Thursday, 9/4/03 09:44:18 pm [-3] it was compiled from:
| target:compiler/generic/vm-macs.lisp
| Created: Friday, 5/23/03 12:20:37 am [-3]
| Comment: $Header: /home/anoncvs/CVS-cmucl/src/compiler/generic/vm-macs.lisp,v 1.19 2002/10/07 14:31:07 toy Exp $

However, I think my difficulty was with a constant defined
with this macro. For example:

| * (describe 'x86:even-fixnum-type)
|
| EVEN-FIXNUM-TYPE is an external symbol in the X86 package.
| It is a constant; its value is 0.

Here, DESCRIBE did not say where the constant is defined.
(It is defined in target:compiler/generic/objdef.lisp.)
This was with CMUCL 18e from 2003, and was not specific
to X86::DEFENUM; plain DEFCONSTANT had the same problem.
Are later versions more helpful?

Rob Warnock

unread,
Dec 31, 2009, 9:01:32 PM12/31/09
to
Kalle Olavi Niemitalo <k...@iki.fi> wrote:
+---------------
| rp...@rpw3.org (Rob Warnock) writes:
| > However, in CMUCL APROPOS and/or DESCRIBE can be very useful for
| > finding stuff, [...]
|
| DESCRIBE will indeed name the source file of a function or macro:
| | * (describe 'x86::defenum)
...

| | On Thursday, 9/4/03 09:44:18 pm [-3] it was compiled from:
| | target:compiler/generic/vm-macs.lisp
...

| However, I think my difficulty was with a constant defined
| with this macro. For example:
|
| | * (describe 'x86:even-fixnum-type)
| |
| | EVEN-FIXNUM-TYPE is an external symbol in the X86 package.
| | It is a constant; its value is 0.
|
| Here, DESCRIBE did not say where the constant is defined.
| (It is defined in target:compiler/generic/objdef.lisp.)
| This was with CMUCL 18e from 2003, and was not specific
| to X86::DEFENUM; plain DEFCONSTANT had the same problem.
| Are later versions more helpful?
+---------------

Yes, actually!! Any version of "19a-pre3" or later should give you this:

cmu> (describe 'x86:even-fixnum-type)

EVEN-FIXNUM-TYPE is an external symbol in the X86 package.
It is a constant; its value is 0.

It is defined in:
target:compiler/generic/objdef.lisp
cmu>

As you can probably guess by now, however, grepping for "even-fixnum-type"
in that file will only show you the EXPORT, not the definition, which
is constructed from a shorter name with DEFENUM (circa line 97) [which
is the whole point of this sub-thread (*sigh*)]:

;;; The main types. These types are represented by the
;;; low three bits of the pointer or immediate object.
;;;
(defenum (:suffix -type)
even-fixnum ; <=== EVEN-FIXNUM-TYPE is defined here.
function-pointer
other-immediate-0
list-pointer
odd-fixnum
instance-pointer
other-immediate-1
other-pointer)

But at least DESCRIBE says which file to look in... ;-} ;-}

budden

unread,
Jan 19, 2010, 7:14:07 AM1/19/10
to
I see null object is a bit problematic in CL. We would like
to declare types, but to use &optional and &key with no defaults
also. So, null object pattern seem to be appropriate. How
to implement it? I'd suggest

(defvar *null-objects*
(make-hash-table :test 'equalp)
"maps type to its null object")

(defmacro with-typed-object-2 ((var type initform
&optional (or (gethash type *null-objects*) (error)))
&body body)
`(let ((,var (or ,initform ,null-object)))
(declare (type ,type ,var))
; do syntax sugaring:
; var.name => (symbol-name var) etc.
,@(replace-var.value body)
))

Comments/critics/suggestions/prior art references?

Günther Thomsen

unread,
Jan 20, 2010, 6:21:15 PM1/20/10
to
On Dec 28 2009, 1:34 am, Lars Rune Nøstdal <larsnost...@gmail.com>
wrote:
[..]

> Hm, sort of related I guess(?) -- I've been using this at times:
>
> CL-USER> (set-macro-character #\¤
>                               (lambda (stream char)
>                                 (declare (ignore char))
>                                 `(slot-value %with-object ',(read
> stream)))
>                               t)
> T
>
[..]
Non ASCII character in source code? Eek. Not even my webbrowser
renders this(or does it? hard to tell), my shell wont for sure.
There are many pretty characters outside ASCII, but using them doesn't
necessarily makes the code easier to read (see APL for an extreme
example) and causes often difficulties for others to render or enter
them. It can be frustrating, that ASCII is the largest common
denominator, but for the time being, we have to live with it.

Pascal J. Bourguignon

unread,
Jan 20, 2010, 7:49:59 PM1/20/10
to
G�nther Thomsen <guen...@gmail.com> writes:

> On Dec 28 2009, 1:34�am, Lars Rune N�stdal <larsnost...@gmail.com>

At least 10 years too late. Now the largest common denominator is unicode.

That said, it could have been written as:

(set-macro-character #\CURRENCY_SIGN ...)

Tim Bradshaw

unread,
Jan 21, 2010, 7:31:47 PM1/21/10
to
On 2010-01-19 12:14:07 +0000, budden <budde...@mail.ru> said:

> I see null object is a bit problematic in CL. We would like
> to declare types, but to use &optional and &key with no defaults
> also. So, null object pattern seem to be appropriate. How
> to implement it? I'd suggest

Why not do what everyone has done for ever:

(declare (type (or null my-type) x))

Kaz Kylheku

unread,
Jan 21, 2010, 8:02:25 PM1/21/10
to
On 2010-01-19, budden <budde...@mail.ru> wrote:
> I see null object is a bit problematic in CL.

The null object pattern is a reinvention of NIL and the NULL type in languages
that don't have it.

The fact that you can do (car nil) -> nil, is an example of the null pattern at
work. A nil value is allowed to be substituted as the parameter of
a function which reads the CAR field of a cons.

Sometimes you want to be able to express ``no value'' or ``invalid value'',
etc. where an object is expected.

But in some type systems, where an object is expected, it must be a reference
conforming to a static type.

So you must actually create a type which can substitute there and carries all
of the expected methods.

So you must factor out a common base class, and make a derived class
out of it for null objects.

class Object ...

class NullObject : implements Object ... // or public Object, whatever

class ActualObject : implements Object ...

So now you can have references to NullObject which substitute in places
that expect references to Object.

It's a big hack; you don't have a single null object type you can just
substitute anywhere.

> We would like to declare types,

What, so there is two of you now? Oh great.

> but to use &optional and &key with no defaults

An optional with no default is ... ta da: a required parameter.

Defaulting is the essence of an optional parameter, which makes it optional.

You can have required key parameter: just check that it was supplied
and bail.

(defun myfunc (&key (required-key nil required-key-passed-p))
(unless required-key-passed-p
(error "MYFUNC: keyword argument required")))

You're the same guy as gavino, right?

> also. So, null object pattern seem to be appropriate. How
> to implement it? I'd suggest

The null object in Lisp is the value nil, which is an instance of the
class null (and type null).

> (defvar *null-objects*
> (make-hash-table :test 'equalp)
> "maps type to its null object")

This is just too stupid for words. Values are not typed references in Lisp.
Memory locations like variables and slots don't carry type; you can store NIL
value in a place that can hold a string, or list, etc.

> Comments/critics/suggestions/prior art references?

(defmethod my-gen-func ((x widget))
;; my generic function specialized to widgets
)

Now the call (my-gen-func <some-widget>) goes to the above method. Others don't
work. In comes the null method:

(defmethod my-gen-func ((x null))
;; my generic function specialized to null object
)

Having the above, we can call (my-gen-func nil). Null pattern done. We can use
nil in any code that needs an object to which my-gen-func is applicable.

Furthermore, we can define a catch-all method also, which will take
calls that otherwise don't match:

(defmethod my-gen-func (x) ;; same as ((x t))
;; my generic function specialized to anything for
;; which there isn't a more specific match.
)

Thomas A. Russ

unread,
Jan 22, 2010, 12:44:04 PM1/22/10
to
Tim Bradshaw <t...@cley.com> writes:

This works well for documentation purposes, but I don't think any
compiler will be able to generate optimizations based on it.

I suppose that one could borrow a "pattern" from languages like Java
where one often introduces a variable with a new type to hold a cast
result:

(delcare (type (or null my-type) x)
(type my-type y))
(when x
(setq y x)
...
)

--
Thomas A. Russ, USC/Information Sciences Institute

Pascal J. Bourguignon

unread,
Jan 22, 2010, 3:09:04 PM1/22/10
to

I would expect a compiler doing type inference to be able to infer
that x is of type my-type inside the WHEN:

(declare (type (or null my-type) x))

(when x
(check-type x my-type) ; dead code.
...)

Tamas K Papp

unread,
Jan 22, 2010, 3:09:52 PM1/22/10
to
On Fri, 22 Jan 2010 09:44:04 -0800, Thomas A. Russ wrote:

> Tim Bradshaw <t...@cley.com> writes:
>
>> Why not do what everyone has done for ever:
>>
>> (declare (type (or null my-type) x))
>
> This works well for documentation purposes, but I don't think any
> compiler will be able to generate optimizations based on it.

(deftype my-type ()
'(simple-array double-float (*)))

(defun sum-unoptimized (object)
(declare (optimize speed (safety 0)))
(if object
(let ((sum 0d0))
(dotimes (i (length object))
(incf sum (aref object i)))
sum)
0))

(defun sum-optimized (object)
(declare (optimize speed (safety 0)))
(declare (type (or null my-type) object))
(if object
(let ((sum 0d0))
(dotimes (i (length object))
(incf sum (aref object i)))
sum)
0))

(defparameter *a* (make-array 10000000 :element-type 'double-float
:initial-element 12d0))
(time (sum-optimized *a*))

Evaluation took:
0.021 seconds of real time
0.020002 seconds of total run time (0.020002 user, 0.000000 system)
95.24% CPU
50,834,795 processor cycles
3,888 bytes consed

(time (sum-unoptimized *a*))

Evaluation took:
0.482 seconds of real time
0.460029 seconds of total run time (0.440027 user, 0.020002 system)
[ Run times consist of 0.136 seconds GC time, and 0.325 seconds non-GC
time. ]
95.44% CPU
1,217,886,947 processor cycles
320,036,352 bytes consed

SBCL can do everything. SBCL is the Chuck Norris of Lisp compilers.

Tamas

Tim Bradshaw

unread,
Jan 22, 2010, 4:33:46 PM1/22/10
to
On 2010-01-22 17:44:04 +0000, t...@sevak.isi.edu (Thomas A. Russ) said:
>
> This works well for documentation purposes, but I don't think any
> compiler will be able to generate optimizations based on it.

I think a Lisp implementation which can't work out that with a type
declaration like (OR NULL x) then in the branch of a conditional where
the object is not NIL, then it's an x probably should be deeply ashamed
of itself. That kind of type inference is not new technology any more.

Madhu

unread,
Jan 22, 2010, 5:58:56 PM1/22/10
to

* Tamas K Papp <7ruf0f...@mid.individual.net> :
Wrote on 22 Jan 2010 20:09:52 GMT:

If only people who post benchmarks knew how to perform them or what they
were comparing.

An example which sought to address Thomas' concern would have made a
comparison between two functions one of which had

(declare (type (or null my-type) object))

vs one that had a

(locally (declare (type my-type object)) ... )

- the names are misleading from the start. Both functions are
"optimized". One is biased towards the result you want to exhibit.
Modifying the code would make what is being indicated even more
misleading

- you do not include the compiler warnings which would have clearly
shown what was and was not known during the type inference phase of
the compiler.

This was what the concern was all about.


| (defparameter *a* (make-array 10000000 :element-type 'double-float
| :initial-element 12d0))
| (time (sum-optimized *a*))
|
| Evaluation took:
| 0.021 seconds of real time
| 0.020002 seconds of total run time (0.020002 user, 0.000000 system)
| 95.24% CPU
| 50,834,795 processor cycles
| 3,888 bytes consed
|
| (time (sum-unoptimized *a*))
|
| Evaluation took:
| 0.482 seconds of real time
| 0.460029 seconds of total run time (0.440027 user, 0.020002 system)
| [ Run times consist of 0.136 seconds GC time, and 0.325 seconds non-GC
| time. ]
| 95.44% CPU
| 1,217,886,947 processor cycles
| 320,036,352 bytes consed

- instead you produce numbers (including GC time) and draw an irrelevant
conclusion

| SBCL can do everything. SBCL is the Chuck Norris of Lisp compilers.


Your academic background in economics is showing.

--
Madhu

mdj

unread,
Jan 22, 2010, 6:52:01 PM1/22/10
to
On 23 Jan, 08:58, Madhu <enom...@meer.net> wrote:

> |>> Why not do what everyone has done for ever:
> |>>
> |>> (declare (type (or null my-type) x))
> |>
> |> This works well for documentation purposes, but I don't think any
> |> compiler will be able to generate optimizations based on it.

[snip]

> | (time (sum-optimized *a*))
> |
> | Evaluation took:
> |   0.021 seconds of real time
> |   0.020002 seconds of total run time (0.020002 user, 0.000000 system)
> |   95.24% CPU
> |   50,834,795 processor cycles
> |   3,888 bytes consed
> |  
> | (time (sum-unoptimized *a*))
> |
> | Evaluation took:
> |   0.482 seconds of real time
> |   0.460029 seconds of total run time (0.440027 user, 0.020002 system)
> |   [ Run times consist of 0.136 seconds GC time, and 0.325 seconds non-GC
> | time. ]
> |   95.44% CPU
> |   1,217,886,947 processor cycles
> |   320,036,352 bytes consed
>
> - instead you produce numbers (including GC time) and draw an irrelevant
>   conclusion

How is this irrelevant? It seems to me it contradicts the PP's comment
quite nicely. Perhaps I am missing something?

Pillsy

unread,
Jan 22, 2010, 7:39:37 PM1/22/10
to
On Jan 22, 5:58 pm, Madhu <enom...@meer.net> wrote:

> * Tamas K Papp <7ruf0fFg4...@mid.individual.net> :


> Wrote on 22 Jan 2010 20:09:52 GMT:

> | On Fri, 22 Jan 2010 09:44:04 -0800, Thomas A. Russ wrote:

[...]

Such a comparison would show the two times to be virtually identical.

> - the names are misleading from the start. Both functions are
>   "optimized".  One is biased towards the result you want to exhibit.
>   Modifying the code would make what is being indicated even more
>   misleading

> - you do not include the compiler warnings which would have clearly
>   shown what was and was not known during the type inference phase of
>   the compiler.

The only notes on either the LOCALLY version or the version with the
OR type declaration are the typical one about boxing the final double-
float answer.

The notes for the unoptimized version have all the usual complaints
about not being able to make AREF and + fast because the array element
type can't be inferred.

> This was what the concern was all about.

> | (defparameter *a* (make-array 10000000 :element-type 'double-float
> |                               :initial-element 12d0))
> | (time (sum-optimized *a*))
> |
> | Evaluation took:
> |   0.021 seconds of real time
> |   0.020002 seconds of total run time (0.020002 user, 0.000000 system)
> |   95.24% CPU
> |   50,834,795 processor cycles
> |   3,888 bytes consed
> |  
> | (time (sum-unoptimized *a*))
> |
> | Evaluation took:
> |   0.482 seconds of real time
> |   0.460029 seconds of total run time (0.440027 user, 0.020002 system)
> |   [ Run times consist of 0.136 seconds GC time, and 0.325 seconds non-GC
> | time. ]
> |   95.44% CPU
> |   1,217,886,947 processor cycles
> |   320,036,352 bytes consed

> - instead you produce numbers (including GC time) and draw an irrelevant
>   conclusion

How is the GC time irrelevant when one solution conses 320 megabytes
because those double-floats returned by AREF need to be boxed, and the
other conses effectively zilch?

Cheers,
Pillsy

Thomas A. Russ

unread,
Jan 22, 2010, 8:57:36 PM1/22/10
to
Madhu <eno...@meer.net> writes:

Point taken. But in this case it turns out not to matter, and addresses
the general question that I has which was if an (or NULL <sometype>)
declaration would be any better than no declaration at all.

It turns out that using SBCL and including a separate optimized function
of the type

> - the names are misleading from the start. Both functions are
> "optimized". One is biased towards the result you want to exhibit.
> Modifying the code would make what is being indicated even more
> misleading
>
> - you do not include the compiler warnings which would have clearly
> shown what was and was not known during the type inference phase of
> the compiler.

The timing differences noted below were sufficiently dramatic that I
think that the point was carried without a detailed analysis.

In any case, I did try out a more direct comparison using

(defun sum-optimized2 (object)


(declare (optimize speed (safety 0)))
(declare (type (or null my-type) object))
(if object

(locally (declare (type my-type object))

(let ((sum 0d0))
(dotimes (i (length object))
(incf sum (aref object i)))

sum))
0))

and found that there was no difference in the assembly code that the
SBCL compiler produced.



> This was what the concern was all about.
>
> | (defparameter *a* (make-array 10000000 :element-type 'double-float
> | :initial-element 12d0))
> | (time (sum-optimized *a*))
> |
> | Evaluation took:
> | 0.021 seconds of real time
> | 0.020002 seconds of total run time (0.020002 user, 0.000000 system)
> | 95.24% CPU
> | 50,834,795 processor cycles
> | 3,888 bytes consed
> |
> | (time (sum-unoptimized *a*))
> |
> | Evaluation took:
> | 0.482 seconds of real time
> | 0.460029 seconds of total run time (0.440027 user, 0.020002 system)
> | [ Run times consist of 0.136 seconds GC time, and 0.325 seconds non-GC
> | time. ]
> | 95.44% CPU
> | 1,217,886,947 processor cycles
> | 320,036,352 bytes consed
>
> - instead you produce numbers (including GC time) and draw an irrelevant
> conclusion
>
> | SBCL can do everything. SBCL is the Chuck Norris of Lisp compilers.

Well, SBCL certainly does this type inference well. I guess I should
have expected that a common (or NULL <type>) declaration would be
something that would be tracked, since it would perhaps be a common
idiom.

This is actually great news.

Madhu

unread,
Jan 22, 2010, 9:23:33 PM1/22/10
to

* Pillsy <42c8e250-f9a4-4804...@a13g2000vbf.googlegroups.com> :
Wrote on Fri, 22 Jan 2010 16:39:37 -0800 (PST):

| On Jan 22, 5:58pm, Madhu <enom...@meer.net> wrote:
|
|> * Tamas K Papp <7ruf0fFg4...@mid.individual.net> :
|> Wrote on 22 Jan 2010 20:09:52 GMT:
|

|> If only people who post benchmarks knew how to perform them or what
|> they were comparing.
|
|> An example which sought to address Thomas' concern would have made a
|> comparison between two functions one of which had
|
|> (declare (type (or null my-type) object))
|
|> vs one that had a
|
|> (locally (declare (type my-type object)) ... )
|
| Such a comparison would show the two times to be virtually identical.

And that the result would be more accurate in addressing one of TAR's
concern, in indicating that "an implementation can work with a (OR NULL
X) declaration do the SAME optimizations as a (TYPE X) declaration, on a
NIL branch,"

Anyway the compiler notes would have indicated that already without
TIME.

|> you do not include the compiler warnings which would have clearly
|> shown what was and was not known during the type inference phase of
|> the compiler.
|
| The only notes on either the LOCALLY version or the version with the
| OR type declaration are the typical one about boxing the final double-
| float answer.
|
| The notes for the unoptimized version have all the usual complaints
| about not being able to make AREF and + fast because the array element
| type can't be inferred.

`unoptimized' is a bad name for this "control case", but this indicates
that this version (without ANY type declarations) is going to cons
megabytes.

[snip]

|> instead you produce numbers (including GC time) and draw an
|> irrelevant conclusion
|
| How is the GC time irrelevant when one solution conses 320 megabytes
| because those double-floats returned by AREF need to be boxed, and the
| other conses effectively zilch?

It is not of course if your only conclusion is that "There is GC", or
"There is boxing", in which case the precise number is also not
irrelevant. (The comment on reporting GC times as a part of the
benchmarks was parenthetical).

I am not disputing the fact exhibited. NOTE my objections were to
1) the methodology
2) relevance of choice of example to state a conclusion
that addresses TAR's concern.
3) Withholding of compiler warnings --the information which would
directly --- even on the given examples --- stated what (w.r.t. TAR's
concerns) was being optimized instead of providing performance numbers
and making conclusions on "what the compiler did"

For (2) The way I understood TAR's concern was its about cases where a
(DECLARE (OR NULL X)) statement could not be used by the compiler to
generate optimized code after type inference. The examples exhibited do
not invalidate this concern while showing a specific case in a specific
implementation where specific type inference happens. At the same time
an example has NOT been provided (it should be easy to construct) where
type inference is NOT done despite a such declaration. The "takeaway"
from the `conclusion' cannot be that these do not exist, and TAR's
concern is not valid.

--
Madhu

Madhu

unread,
Jan 22, 2010, 11:02:30 PM1/22/10
to

* (Thomas A. Russ) <ymiockl...@blackcat.isi.edu> :
Wrote on 22 Jan 2010 17:57:36 -0800:

|> If only people who post benchmarks knew how to perform them or what they
|> were comparing.
|>
|> An example which sought to address Thomas' concern would have made a
|> comparison between two functions one of which had
|>
|> (declare (type (or null my-type) object))
|>
|> vs one that had a
|>
|> (locally (declare (type my-type object)) ... )
|
| Point taken. But in this case it turns out not to matter, and addresses
| the general question that I had which was if an (or NULL <sometype>)

| declaration would be any better than no declaration at all.

I interpreted your concern more broadly in my followup to Pillsy (which
I was not able to supersede) in the last paragraph --- the question I
believed you were asking was: whether tacking on an (OR NULL) to a
declaration would kill any optimizations that would have otherwised
happened.

If this was not what you implied when you posted, I apologize for
(mis)stating your position like that, However I'll let my remarks stand
without retracting them -- (after correcting the typos, i wish)...

|> - you do not include the compiler warnings which would have clearly
|> shown what was and was not known during the type inference phase of
|> the compiler.
|
| The timing differences noted below were sufficiently dramatic that I
| think that the point was carried without a detailed analysis.
|
| In any case, I did try out a more direct comparison using
|
| (defun sum-optimized2 (object)
| (declare (optimize speed (safety 0)))
| (declare (type (or null my-type) object))
| (if object
| (locally (declare (type my-type object))
| (let ((sum 0d0))
| (dotimes (i (length object))
| (incf sum (aref object i)))
| sum))
| 0))
|
| and found that there was no difference in the assembly code that the
| SBCL compiler produced.

The fact that this implementation does optimizations based on type
inference correctly on the IF branch is clear; But there can be
optimizations that a (TYPE X) declaration might produce, which a (TYPE
(OR NULL X)) will lose (ISTR seeing this in CMUCL) -- perhaps use
`object' in the other IF branch, or SETQ it conditionally on some random
condition, or lose the IF statement.

--
Madhu

mdj

unread,
Jan 23, 2010, 5:50:39 AM1/23/10
to
On 23 Jan, 14:02, Madhu <enom...@meer.net> wrote:

> | Point taken.  But in this case it turns out not to matter, and addresses
> | the general question that I had which was if an (or NULL <sometype>)
> | declaration would be any better than no declaration at all.
>
> I interpreted your concern more broadly in my followup to Pillsy (which
> I was not able to supersede) in the last paragraph --- the question I
> believed you were asking was: whether tacking on an (OR NULL) to a
> declaration would kill any optimizations that would have otherwised
> happened.
>
> If this was not what you implied when you posted, I apologize for
> (mis)stating your position like that, However I'll let my remarks stand
> without retracting them -- (after correcting the typos, i wish)...

Perhaps you might consider extending your apology to the people that
your venomous and apparently irrelevant responses where directed to ?

Matt

budden

unread,
Jan 24, 2010, 4:05:32 AM1/24/10
to
Hi!
Thank you, Madhu, for your
apologies but, first of all, for your
deep view on the matter. Thank you, Tamas, for
the example. Thank you, Thomas, for
understanding. Shame on you, Kaz, for your
agressive and mostly irrelevant reply :)
SBCL is very good in optimization. But SBCL
is not the only CL implementation.
If someone states that (or type null) can
always be optimized, he should check this
in other implementations too. I don't know
that and I was too lazy to check.
And also if one read the entire topic,
he would find that initially I was trying
to remove extraneous defstruct verbosity.
Let me quote myself:

>(with-typed-object (x (make-instance 'universe) universe)

> (x.some-method x-->half-truth-a))

>Which would expand as

>(let ((x (make-instance 'universe)))
> (declare (type universe x))
> (universe-some-method (slot-value x 'half-truth-a)))

In this situation, declaring (type universe x)
would fail if we pass NIL.

Declaring (type (or universe null))
would not enable optimization unless
we write (if universe ...) in body.
So if it is known already that universe
is not null, we either lose optimizations
or we need to write extra when's.

NULL object pattern is, I guess, a good
way to take over this obstacle.

>I suppose that one could borrow a "pattern"
>from languages like Java where one often
>introduces a variable with a new type to
>hold a cast result:
>
> (delcare (type (or null my-type) x)
> (type my-type y))
> (when x
> (setq y x)
> ...
> )

Got no SBCL at hand, but I think this
would fail: y should be initialized to
some value of type my-type, what is
its value before first setq? Also this
is not a concise style. We need two
names for one object.

0 new messages