I have a couple models where I'd like to have unbound slots that normally have cv cells in them. NIL is a valid value, so I can't just say "nil is the invalid value." What I'm doing is using a special invalid-value object when I'd normally have an unbound slot:
(defconstant +unbound+ '+unbound+) (deftype %nil () '(or nil (eql +unbound+)))
(defmacro define-unbound-methods (&body slot-specs) (loop for (class slot opt-accessor) in slot-specs for accessor = (or opt-accessor slot) collect `(defmethod ,accessor :around ((object ,class)) (let ((value (call-next-method))) (if (eq value +unbound+) (error 'slot-unbound :instance object :name ',slot) value))) into methods finally (return `(progn ,@methods))))
(defun cv_ () (cv +unbound+))
(defmodel my-handler (araneida:handler) ((user-id :accessor user-id :initarg user-id :initform (cv_) :documentation "user id or NIL if the user didn't supply valid credentials") (homepage :accessor homepage :initarg homepage :initform (c?_ (aif (id (^user-id)) (make-homepage-for-user id) (create-user-account))))))
(define-unbound-methods (my-handler userid))
(defmethod handle-request-authentication ((handler my-handler) method request) (setf (user-id h) ...))
If user-id is unbound, it means that authentication hasn't been performed yet, so it would be a program error to try to use its value at that point.
It seems like it wouldn't be too hard to put the concept of unbound slots into Cells itself, so that storing the unbound-slot value would work fine, but reading it would signal a slot-unbound error. Add cell-makunbound and cell-boundp, and it would be the normal CLOS semantics again.
Does this sound like a good idea? Or is there a more idiomatically Cells way of doing this, and I'm wandering too far down The Dark Path of Lazyness?
Thomas F. Burdick wrote:
I have a couple models where I'd like to have unbound slots that normally have cv cells in them. NIL is a valid value, so I can't just say "nil is the invalid value." What I'm doing is using a special invalid-value object when I'd normally have an unbound slot:
(defconstant +unbound+ '+unbound+) (deftype %nil () '(or nil (eql +unbound+)))
(defmacro define-unbound-methods (&body slot-specs) (loop for (class slot opt-accessor) in slot-specs for accessor = (or opt-accessor slot) collect `(defmethod ,accessor :around ((object ,class)) (let ((value (call-next-method))) (if (eq value +unbound+) (error 'slot-unbound :instance object :name ',slot) value))) into methods finally (return `(progn ,@methods))))
(defun cv_ () (cv +unbound+))
(defmodel my-handler (araneida:handler) ((user-id :accessor user-id :initarg user-id :initform (cv_) :documentation "user id or NIL if the user didn't supply valid credentials") (homepage :accessor homepage :initarg homepage :initform (c?_ (aif (id (^user-id)) (make-homepage-for-user id) (create-user-account))))))
(define-unbound-methods (my-handler userid))
(defmethod handle-request-authentication ((handler my-handler) method request) (setf (user-id h) ...))
If user-id is unbound, it means that authentication hasn't been performed yet, so it would be a program error to try to use its value at that point.
It seems like it wouldn't be too hard to put the concept of unbound slots into Cells itself, so that storing the unbound-slot value would work fine, but reading it would signal a slot-unbound error. Add cell-makunbound and cell-boundp, and it would be the normal CLOS semantics again.
Does this sound like a good idea? Or is there a more idiomatically Cells way of doing this, and I'm wandering too far down The Dark Path of Lazyness?
It might be the Dark Path of a long, well-respected, misbegotten tradition of collapsing two attributes into one, creating /extra/ work to save a slot. The first of the two slots is 'sign-in-status", with three values: not-yet, failed, or succeeded. Then there is another slot, which indicates who signed in (iff successful). We programmers have a long heritage and habit of collapsing two values into one to save memory or disk space (remember those?) by using some trick such as "use nil for failure, unbounditude for not-yet, non-nil bound for the user", but my experience has been that life gets a lot easier if I just let the diff attributes be diff slots.
It is certainly tempting to "save one slot", but, again, my experience has been that the consequent overloading saves a slot at the expense of forever complexifying the code. I can't just say (ecase (sign-in-status self) (:not-yet..)(:failed..)(:cool...))... I have to detect one expected value with 'cell-unboundp and the other two with ecase, every place in the code I need to access the user.
kt
On the other hand, Cells should not change Lisp unnecessarily.
It would be easy enough to do something such as (cv +unbound+) or just (cv) and then have the internal sm-install function invoke slot-makunbound instead of forcing the slot to be bound. Then one does not need a special test for cell-boundp (which sounds wrong anyway in re transparency).
But what about a rule that runs across an unbound cell/slot? I should think that does not generate an unbound error, rather the slot mediated by the rule should in turn be made unbound.
I guess echoing works OK, tho anyway doing this on the GUI slots will not get far since existing echos are not testing for unbounditude.
The big hole is that slot-makunbound does not go through (setf /accessor/), and slot-makunbound-using-class is not portable, so we would need to lose some transparency and have an exported c-slot-makunbound function to make the slot unbound and kick off propagation (and confirm that the slot is c-variable mediated).
thoughts?
kt
Kenny Tilton wrote:
Thomas F. Burdick wrote:
I have a couple models where I'd like to have unbound slots that normally have cv cells in them. NIL is a valid value, so I can't just say "nil is the invalid value." What I'm doing is using a special invalid-value object when I'd normally have an unbound slot:
(defconstant +unbound+ '+unbound+) (deftype %nil () '(or nil (eql +unbound+)))
(defmacro define-unbound-methods (&body slot-specs) (loop for (class slot opt-accessor) in slot-specs for accessor = (or opt-accessor slot) collect `(defmethod ,accessor :around ((object ,class)) (let ((value (call-next-method))) (if (eq value +unbound+) (error 'slot-unbound :instance object :name ',slot) value))) into methods finally (return `(progn ,@methods))))
(defun cv_ () (cv +unbound+))
(defmodel my-handler (araneida:handler) ((user-id :accessor user-id :initarg user-id :initform (cv_) :documentation "user id or NIL if the user didn't supply valid credentials") (homepage :accessor homepage :initarg homepage :initform (c?_ (aif (id (^user-id)) (make-homepage-for-user id) (create-user-account))))))
(define-unbound-methods (my-handler userid))
(defmethod handle-request-authentication ((handler my-handler) method request) (setf (user-id h) ...))
If user-id is unbound, it means that authentication hasn't been performed yet, so it would be a program error to try to use its value at that point.
It seems like it wouldn't be too hard to put the concept of unbound slots into Cells itself, so that storing the unbound-slot value would work fine, but reading it would signal a slot-unbound error. Add cell-makunbound and cell-boundp, and it would be the normal CLOS semantics again.
Does this sound like a good idea? Or is there a more idiomatically Cells way of doing this, and I'm wandering too far down The Dark Path of Lazyness?
It might be the Dark Path of a long, well-respected, misbegotten tradition of collapsing two attributes into one, creating /extra/ work to save a slot. The first of the two slots is 'sign-in-status", with three values: not-yet, failed, or succeeded. Then there is another slot, which indicates who signed in (iff successful). We programmers have a long heritage and habit of collapsing two values into one to save memory or disk space (remember those?) by using some trick such as "use nil for failure, unbounditude for not-yet, non-nil bound for the user", but my experience has been that life gets a lot easier if I just let the diff attributes be diff slots.
It is certainly tempting to "save one slot", but, again, my experience has been that the consequent overloading saves a slot at the expense of forever complexifying the code. I can't just say (ecase (sign-in-status self) (:not-yet..)(:failed..)(:cool...))... I have to detect one expected value with 'cell-unboundp and the other two with ecase, every place in the code I need to access the user.
kt
Kenny Tilton writes:
It might be the Dark Path of a long, well-respected, misbegotten tradition of collapsing two attributes into one, creating /extra/ work to save a slot. The first of the two slots is 'sign-in-status", with three values: not-yet, failed, or succeeded. Then there is another slot, which indicates who signed in (iff successful). We programmers have a long heritage and habit of collapsing two values into one to save memory or disk space (remember those?) by using some trick such as "use nil for failure, unbounditude for not-yet, non-nil bound for the user", but my experience has been that life gets a lot easier if I just let the diff attributes be diff slots.
It is certainly tempting to "save one slot", but, again, my experience has been that the consequent overloading saves a slot at the expense of forever complexifying the code. I can't just say (ecase (sign-in-status self) (:not-yet..)(:failed..)(:cool...))... I have to detect one expected value with 'cell-unboundp and the other two with ecase, every place in the code I need to access the user.
My hesitation to do this is that I don't want worry about authentication outside of the authentication phase. Having an unbound slot seems a nice way to deal with it because it expresses the idea that asking for a user-id is not a valid question right now. It's perfectly legitimate for a user to not be logged in, but I want to make sure this has been checked.
I guess I could do something like this:
(defmodel my-handler () ((auth-status :accessor auth-status :initarg auth-status :initform (cv :not-authenticated)) (%user-id :accessor %user-id :initarg %user-id :initform (cv nil)) (user-id :accessor user-id :initarg user-id :initform (c? (assert (eql (^auth-status) :authenticated)) (^%user-id)))))
(defmethod authenticate ((request my-handler)) (let ((user-id ...)) (setf (auth-status request) :authenticated (%user-id request) user-id)))
However ...
Kenny Tilton writes:
On the other hand, Cells should not change Lisp unnecessarily.
It would be easy enough to do something such as (cv +unbound+) or just (cv) and then have the internal sm-install function invoke slot-makunbound instead of forcing the slot to be bound. Then one does not need a special test for cell-boundp (which sounds wrong anyway in re transparency).
But what about a rule that runs across an unbound cell/slot? I should think that does not generate an unbound error, rather the slot mediated by the rule should in turn be made unbound.
I guess echoing works OK, tho anyway doing this on the GUI slots will not get far since existing echos are not testing for unbounditude.
... I like this, because if you have rules that ultimately depend on an uncalculated value, you'll get an error when you try to ask for it.
The big hole is that slot-makunbound does not go through (setf /accessor/), and slot-makunbound-using-class is not portable, so we would need to lose some transparency and have an exported c-slot-makunbound function to make the slot unbound and kick off propagation (and confirm that the slot is c-variable mediated).
thoughts?
This is actually why I stopped at the cheesy hack level with what I had (that, and I wanted to get back to work). c-slot-makunbound could just dtrt if it's given an object that doesn't use cells; then it wouldn't be so bad to use it, you could just use it everywhere. You definately end out with a transperancy problem wrt CLOS one way or the other, unless you use the MOP.
Thomas F. Burdick wrote:
Kenny Tilton writes:
On the other hand, Cells should not change Lisp unnecessarily.
It would be easy enough to do something such as (cv +unbound+) or just (cv) and then have the internal sm-install function invoke slot-makunbound instead of forcing the slot to be bound. Then one does not need a special test for cell-boundp (which sounds wrong anyway in re transparency).
But what about a rule that runs across an unbound cell/slot? I should think that does not generate an unbound error, rather the slot mediated by the rule should in turn be made unbound.
I guess echoing works OK, tho anyway doing this on the GUI slots will not get far since existing echos are not testing for unbounditude.
... I like this, because if you have rules that ultimately depend on an uncalculated value, you'll get an error when you try to ask for it.
Yes, I am thinking we throw a c-slot-unbound condition from the generated cell slot accessor. The internals code that kicks off a rule can trap for just that condition, so other non-cell unbound errors go splat normally. and user-level slot accesses would backtrace on c-slot-unbound. too bad there is no Lisp condition to be subclassed (guessing).
kt
Kenny Tilton writes:
Thomas F. Burdick wrote:
Kenny Tilton writes:
On the other hand, Cells should not change Lisp unnecessarily.
It would be easy enough to do something such as (cv +unbound+) or just (cv) and then have the internal sm-install function invoke slot-makunbound instead of forcing the slot to be bound. Then one does not need a special test for cell-boundp (which sounds wrong anyway in re transparency).
But what about a rule that runs across an unbound cell/slot? I should think that does not generate an unbound error, rather the slot mediated by the rule should in turn be made unbound.
I guess echoing works OK, tho anyway doing this on the GUI slots will not get far since existing echos are not testing for unbounditude.
... I like this, because if you have rules that ultimately depend on an uncalculated value, you'll get an error when you try to ask for it.
Yes, I am thinking we throw a c-slot-unbound condition from the generated cell slot accessor. The internals code that kicks off a rule can trap for just that condition, so other non-cell unbound errors go splat normally. and user-level slot accesses would backtrace on c-slot-unbound. too bad there is no Lisp condition to be subclassed (guessing).
Heh, you missed it in my first message: unbound-slot.