I have an object to encapsulate server state, since I don't see
limiting the library to one server at a time is a good design
decision.
In the function I use to create a server object and connect to it and
so on, I use backquote in hat I found to be a quite elegant way, but
I'm suddenly slightly unsure on exactly how well that will work.
What I've done before is to push values and initarg keywords onto a
list and then use APPLY as below to create the object, but I do
actually think this style is easier to read. I am suddenly, however,
not sure it will work in a compiled environment. I think it will, but
I thought I'd better ask before I rely on it.
;;; Class definition
(defclass server ()
((name :reader name :initarg :name)
(port :reader port :initarg :port)
(stream :accessor stream :initarg :stream)
(groups :accessor groups :initarg :groups)
(current :accessor current :initarg :current)
(username :reader username :initarg :username)
(password :reader password :initarg :password)
(saved :accessor saved :initarg :saved))
(:default-initargs :port 119 :stream nil :groups nil :saved nil))
;;; function to connect to a server and return a server object
(defun connect-to-server (servername &key (port 119)
(password nil password-supplied-p)
(username nil username-supplied-p))
(let ((stream (open-nntp-stream servername port)))
(let ((options `(:name ,servername :port ,port :stream ,stream
,@(when password-supplied-p `(,password))
,@(when username-supplied-p `(,username)))))
(when stream
(let ((server (apply #'make-instance 'server options)))
(multiple-value-bind (rv response) (read-response server)
(cond ((<= 200 rv 201)
(setf *current-server* server)
(setf (groups server) (list-all-groups server))
server)
(t (format t "Connecting to ~a gave this error~% ~a ~a"
servername rv response)
nil))))))))
//Ingvar
--
((lambda (x) `(,x ',x)) '(lambda (x) `(,x ',x)))
Probably KMP
[...]
> What I've done before is to push values and initarg keywords onto a
> list and then use APPLY as below to create the object, but I do
> actually think this style is easier to read. I am suddenly, however,
> not sure it will work in a compiled environment. I think it will [...]
It will indeed. It's harder for me to say if this is good style or
not (I have sometimes done a similar thing and then thought that it
might not have been a good idea). But it's all legal. (At least in
theory, what might not be legal in certain cases would be
destructively modifying the value of a backquoted form, but that, I
think, is highly academic.)
In any case, for this particular case, what I would do would be to
define a constructor (MAKE-SERVER) which, of course, calls
MAKE-INSTANCE, but whose lambda-list makes it simpler to call it from
functions such as CONNECT-TO-SERVER. In fact, you can have
MAKE-SERVER as a generic function and use generic function dispatch to
call one method if a username-password pair is supplied, and another
if not.
By the way, does it make sense to supply a name without a password?
And a password without a name is like a smile without a cat... This
implies that you may want to have another class that encapsulates a
username-password pair. This gives you additional benefits, such as
the ability to define a PRINT-OBJECT method that does not print the
password in the clear but prints some indication whether a non-empty
password was supplied or not (in log files, for example). By the way,
Java has a similar class which provides a method to clear the password
once it has been used, for obvious paranoic reasons (I don't think it
provides a toString() methods that hides the password, but I am not
sure).
---Vassil.
> [...]
> ,@(when password-supplied-p `(,password))
> ,@(when username-supplied-p `(,username)))))
Did you mean this?
,@(when password-supplied-p `(:password ,password))
,@(when username-supplied-p `(:username ,username)))))
I can't see any reason why this should not work.
But how about (some variation of) this scheme:
(defun connect-to-server (servername &rest init-args &key (port 119))
(let ((stream (open-nntp-stream servername port)))
(when stream
(let ((server (apply #'make-instance 'server
:name servername
:stream stream
:port port
(remf init-args :port))))
...))))
But wouldn't it be more natural to have connect-to-server take a
server object as parameter, i.e. to have the caller create the server
object himself? Then you'd avoid this issue altogether. At least
create the server object before the stream, so that the stream can be
created from the server-name and port values that resulted from the
initialize-instance process.
--
Frode Vatvedt Fjeld
Yes, as others have pointed out, it's a fine technique. Backquote is
frequently useful whenever you have a complex data structure where much of
it is literal and you need to fill in a few variable fields. Macros are
the most common context where this arises, but it can be used in many other
cases. Remember, a macro is simply an ordinary function whose input and
output happens to be used to manipulate code.
--
Barry Margolin, bar...@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
> Ingvar Mattsson <ing...@cathouse.bofh.se> writes:
>
> [...]
> > What I've done before is to push values and initarg keywords onto a
> > list and then use APPLY as below to create the object, but I do
> > actually think this style is easier to read. I am suddenly, however,
> > not sure it will work in a compiled environment. I think it will [...]
>
> It will indeed. It's harder for me to say if this is good style or
> not (I have sometimes done a similar thing and then thought that it
> might not have been a good idea). But it's all legal. (At least in
> theory, what might not be legal in certain cases would be
> destructively modifying the value of a backquoted form, but that, I
> think, is highly academic.)
Ayup. Cool.
> In any case, for this particular case, what I would do would be to
> define a constructor (MAKE-SERVER) which, of course, calls
> MAKE-INSTANCE, but whose lambda-list makes it simpler to call it from
> functions such as CONNECT-TO-SERVER. In fact, you can have
> MAKE-SERVER as a generic function and use generic function dispatch to
> call one method if a username-password pair is supplied, and another
> if not.
Well, tehre is only one place I need to create server objects and that
is in the function that establishes a connection to a server, if that
makes any sense.
> By the way, does it make sense to supply a name without a password?
> And a password without a name is like a smile without a cat... This
> implies that you may want to have another class that encapsulates a
> username-password pair.
Possibly. However, since this is the only instance (in this library,
at least) where I want usernames and/or passwords, I feel that may be
a bit of an overkill.
> This gives you additional benefits, such as
> the ability to define a PRINT-OBJECT method that does not print the
> password in the clear but prints some indication whether a non-empty
> password was supplied or not (in log files, for example). By the way,
> Java has a similar class which provides a method to clear the password
> once it has been used, for obvious paranoic reasons (I don't think it
> provides a toString() methods that hides the password, but I am not
> sure).
The reason I save username/password is so I can re-establish the
stream if it should be disconnected and it's an NNTP server requiring
authentication, so I don't want to clear it until the session is
explicitly disconnected and at that point, I have to do smoe other
clean-up too.
//Ingvar
--
Sysadmin is brave
Machine is running for now
Backup on Friday
This may be a slightly misleading statement since a macro doesn't
evaluate its arguments the way a 'real' ordinary function does.
I thought it was clear from the context that I was talking about the
operation of the body. The point is that macro bodies are just ordinary
Lisp code, and backquote is a general-purpose list-constructing mechanism
that can be used in any ordinary Lisp code. It was created for use in
macros because they happen to need this type of thing most, but there's
nothing special them that makes them require each other.
Are you concerned that the called function might use &rest to gain
access to the original list and destructively manipulate it?
That can happen, but IMHO that is a problem in the called functions.
Functions should not be written with the assumption that the &rest
list is a fresh copy consed up for each call, unless perhaps the
caller and callee are closely related functions in the same module of
code.
Not academic at all. If you have a backquoted form with a constant
tail section, that can readily be turned into constant data. It's not
inconceivablet that
`(,variable ,variable tail section)
might behave like
(list* variable variable '(tail section))
It's not an unheard of practice to capture keyword parameter lists
using &rest and then to filter them.
(defun fun (&rest key-list &key a b c &allow-other-keys)
;; oops, no copy-list done!
(remf key-list :a)
(remf key-list :b)
(remf key-list :c)
(apply other-fun key-list))
(apply fun `(:a ,a-arg :b 42)) ;; oops, fun does a remf on :b
property.
So if you put the two together, you can see why Ingvar might have
concerns.
Not only is it not inconceivable, it's actually quite common I think. I
believe that was the expansion in the ancient Maclisp backquote
implementation, so I would be surprised if most current implementations
don't do something similar (the main difference being that many
implementations use functions like SYS:BQ-LIST* rather than LIST* -- the
pretty-printer can recognize calls to these functions and print them using
backquote notation).
> Vassil Nikolov <vniko...@poboxes.com> wrote in message news:<kzbzntx...@panix3.panix.com>...
[...]
> > (At least in
> > theory, what might not be legal in certain cases would be
> > destructively modifying the value of a backquoted form, but that, I
> > think, is highly academic.)
>
> Not academic at all.
Yes, you are right. I guess I generalized my own utter reluctance
to destructively modify any list that is produced by a backquoted
form, even if it is quite clear it will be freshly consed.
> (defun fun (&rest key-list &key a b c &allow-other-keys)
> ;; oops, no copy-list done!
> (remf key-list :a)
> (remf key-list :b)
> (remf key-list :c)
> (apply other-fun key-list))
>
> (apply fun `(:a ,a-arg :b 42)) ;; oops, fun does a remf on :b
> property.
Oops indeed, nobody promised that the list in the &REST parameter
would be freshly consed, whether or not there was a call to APPLY
with a backquoted form as the last argument.
Besides, doing it with REMF is somewhat improper: after all,
(APPLY #'FUN `(:A ,ACTUAL-A :B 42 :A 'RED-HERRING)) is a valid
call as far as FUN's arglist is concerned, but OTHER-FUN seems
to need :A removed.
---Vassil.
> Vassil Nikolov <vniko...@poboxes.com> writes:
[...]
> > This gives you additional benefits, such as
> > the ability to define a PRINT-OBJECT method that does not print the
> > password in the clear but prints some indication whether a non-empty
> > password was supplied or not (in log files, for example). By the way,
> > Java has a similar class which provides a method to clear the password
^^^^^^^^^^^^^^^^^
By the way, please read `provides a way' for the above.
> > once it has been used, for obvious paranoic reasons (I don't think it
> > provides a toString() methods that hides the password, but I am not
> > sure).
>
> The reason I save username/password is so I can re-establish the
> stream if it should be disconnected and it's an NNTP server requiring
> authentication, so I don't want to clear it until the session is
> explicitly disconnected and at that point, I have to do smoe other
> clean-up too.
Which is probably fine, it's just that I have seen too many log
files containing passwords in the clear, so I went on a tangent...
---Vassil.
It is still bad practice. You should treat a &rest list as read-only.
| (defun fun (&rest key-list &key a b c &allow-other-keys)
| ;; oops, no copy-list done!
That would be a serious bug.
| So if you put the two together, you can see why Ingvar might have
| concerns.
The traditional Lisp thinking about the ownership protocol is that you
have to be explicit about modifying the structure of your arguments.
Unless you have explicitly made that part of the function definition, a
caller is allowed to assume that the arguments were not modified, which
means that you /never/ break that assumption.
--
Erik Naggum, Oslo, Norway
Act from reason, and failure makes you rethink and study harder.
Act from faith, and failure makes you blame someone and push harder.
This `remf´ things seems to go by without comment, so it must mean that
several people are not aware that only the first keyword-value pair that
matches is actually used. Just passing `init-args´ alone is just fine,
and avoids the problem that you screw with your caller.