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...
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>
Or well, really using NAMED-READTABLES for the syntax stuff;
http://common-lisp.net/project/editor-hints/darcs/named-readtables/doc/named-readtables.html
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>
(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.
> ? (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.
(let ((x (make-instance 'cow)))
(x.run x-->size)))
--
__Pascal Bourguignon__ http://www.informatimago.com/
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.
(let ((x (make-instance 'universe)))
(let ((x (make-instance 'cow)))
(cow-run x (slot-value x 'size))))
>>(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.
Well, ok, I mean patches are welcome :)
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
> 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?
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... ;-} ;-}
(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?
> 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 ...)
> 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))
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.
)
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
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.
...)
> 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
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.
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
> |>> 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?
> * 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
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.
| 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
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
> | 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
>(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.