Make classes this way?

Discussion of Common Lisp
Post Reply
Jasper
Posts: 209
Joined: Fri Oct 10, 2008 8:22 am
Location: Eindhoven, The Netherlands
Contact:

Make classes this way?

Post by Jasper » Tue Apr 07, 2009 7:00 am

Often i use a a method of programming where i do the 'main object' in the first argument. Since i did this so regularly, it seemed silly. A related problem seemed to be that Lisp programs themselves didn't seem like objects.

So i tried to fix this by making a macro, i named program-to-class that (with macro hooks)recognized defvars, defparameters, and made them
into slots of a class. Recognizing defun, defmethod, defgeneric, and adding the argument that is the class (and
type declare for defun) at the start. It also recognizes defclass and defstruct. defmacro is still todo. (Except i am not going to do it anymore)

But then i noticed how defvar and defparameter work a little perculiarly; LET takes them over even when they're used in a function.

Code: Select all

(defparameter *meh* -1)
(defun mah ()
  (print (setf- +  *mah* 1)))
(let ((*mah* -5))
  (mah))
So i can do better, just by taking over global variables:

Code: Select all

(defpackage #:class-var
  (:use #:common-lisp #:iterate)
  (:documentation "Puts all variables in a class, makes all functions have\
 this class as first argument.")
  (:export class-using-vars class-being-vars))

(in-package #:class-var)

(defmacro if-with (var cond true else)
  `(let ((,var ,cond))
     (if ,cond ,true ,else)))

(defmacro if-use (&rest conds)
  (let ((var (gensym)))
    `(if-with ,var ,(car conds)
        ,var ,(if (null (cdr conds))
		(car conds)
		`(if-use ,@(cdr conds))))))

;;The simple way.
(defmacro class-using-var (class-name vars &key types options)
  "Makes a class with given variables."
  `(defclass ,class-name ()
     (,@(iter
	 (for v  in vars)
	 (for tp in (if-use types types (iter (repeat (length vars))
					      (collect nil))))
	 (collect
	     `(,v :initform ,v :initarg ,(intern (symbol-name v) :keyword)
		  ,@(when tp `(:type ,tp)))))
     ,@options)))

(defmacro class-being-vars ((instance &rest vars) &rest body)
  "Lets a class take over the variables. (from defvar/defparameter)
Note that all the vars must be available as slots."
  (let ((inst (gensym)))
    `(let ((,inst ,instance))
       (let (,@(iter (for v in vars) ;Override variables.
		     (collect `(,v (slot-value ,inst ',v)))))
	 ,@body ;Do the body.
	 ,@(iter (for v in vars) ;Write back.
		 (collect `(setf (slot-value ,inst ',v) ,v)))))))
An example of use:

Code: Select all

(use-package :class-var)

(defparameter *meh* -1)
(defparameter *mah* 1)

(class-using-var moeh (*mah* *meh*))

(defvar *inst* (make-instance 'moeh))
(defvar *inst2* (make-instance 'moeh))

(defun meh () (print (setf *meh* (+ *meh* 1))))
(defun mah () (print (setf *mah* (+ *mah* 1))))

(class-being-vars (*inst2* *mah* *meh*)
  (meh) (mah))
Using this would save a lot of first arguments when defining functions, but on the other hand you'll have to use class-being-vars when using it.(Hmm need shorter name) Or make something wrapping it. Another disadvantage is the overhead, of setting the slots afterwards, i am not sure how bad that is. (You can't use with-slots; it uses symbol-macrolet)

I couldn't find any library that does this. Btw cliki should have an application domain called 'macros' or something, where stuff like this, iterate, series, alexandria etc. goes. Edit: a start

ramarren
Posts: 613
Joined: Sun Jun 29, 2008 4:02 am
Location: Warsaw, Poland
Contact:

Re: Make classes this way?

Post by ramarren » Tue Apr 07, 2009 8:07 am

This is a bit similar to contexts contained in misc-extensions. Except those use lexical, rather than special bindings, so they have much better encapsulation, even if it is possibly marginally more verbose (you would need context constructor parametrized with class instance), but that comes with greater generality.
Jasper wrote:But then i noticed how defvar and defparameter work a little perculiarly; LET takes them over even when they're used in a function.
This reads as if you were unaware of what dynamic/special bindings are. If so, I would suggest reading relevant chapter in PCL, as those can be tricky if not used properly.

Jasper
Posts: 209
Joined: Fri Oct 10, 2008 8:22 am
Location: Eindhoven, The Netherlands
Contact:

Re: Make classes this way?

Post by Jasper » Tue Apr 07, 2009 3:02 pm

Thanks, about variables, i probably thought yeah.. yeah i know how to make variables already. I think it should probably re-read a bunch of PCL.

I tried contexts a little, seemed to install fine, but i can't get it to work properly;

Code: Select all

(defun sqr (x) (* x x))
(require :misc-extensions)
(in-package #:lexical-contexts)

(defcontext vector-context (x y)
  (deflex z 0 "z")
  (defun lensqr () (+ (sqr x) (sqr y) (sqr z))))

(with-context (vector-context 4 6)
  (lensqr))
Defcontext works fine, with-context says: "WITH-CONTEXT: undefined context name: VECTOR-CONTEXT". What am i doing wrong? No docstrings on functions/macros either :/.

I looked at the source, maybe it is just a bug, perhaps i will see if i can fix it. I usually don't use (get .. ..), maybe if i manually do it with getf or something. Don't like the source btw; defcontext should've had some functions to help it and seems to be reinventing destructuring-bind.. (But haven't looked good enough to see why, and i guess i reimplemented destructuring-bind in my project too; argumentize-list and do-by-argument)

ramarren
Posts: 613
Joined: Sun Jun 29, 2008 4:02 am
Location: Warsaw, Poland
Contact:

Re: Make classes this way?

Post by ramarren » Wed Apr 08, 2009 12:02 am

DEFCONTEXT expands to EVAL-WHEN without the :execute clause, which means it works only when compiled or loaded, and not from the REPL. I have no idea why is that. The entire thing is largely a hack anyway, since it seems to have not caught on. I haven't used them really, and I the only library in clbuild which uses them is FSet, which is how I noticed contexts in the first place.

I still think they are a neat idea, but somehow other ways of code organization usually feel better.

Jasper
Posts: 209
Joined: Fri Oct 10, 2008 8:22 am
Location: Eindhoven, The Netherlands
Contact:

Re: Make classes this way?

Post by Jasper » Wed Apr 08, 2009 6:11 am

Ramarren wrote:This is a bit similar to contexts contained in misc-extensions. Except those use lexical, rather than special bindings, so they have much better encapsulation, even if it is possibly marginally more verbose (you would need context constructor parametrized with class instance), but that comes with greater generality.
I don't see how contexts are more general then my approach. My approach:
  • works on any structure/class with the right slot-names.(Hmm, i should generalize such that they do not have to be slot-names per-see)
  • You have a structure you can manipulate in the regular way, which i do not see with the defcontext ways.
  • It can treat existing programs as structures/class. (Which could be its main usefulness, imo.)
  • Saves having to the the argument of the structure and a with-slots for it at start of functions.
  • It is really simple; just the #'class-being-vars macro, really.
Main disadvantages:
  • Overhead in setting, overhead in garbage collector?
  • having to use #'defvar/#'defparameter can be annoying, as they are global. However, you can keep them inside packages.
  • Doesn't recognize and #'declare types.(yet?)
  • You have to call it.
Ramarren wrote:I still think they are a neat idea, but somehow other ways of code organization usually feel better.
I tend to agree, but maybe i will experiment with it. If lisp could infer all the types, it could infer the slots, and one wouldn't have to specify the slots all the time in #'class-being-vars, improving that. This is an interesting idea for my project. (It is unsure what types a common lisp implementation would infer)

ramarren
Posts: 613
Joined: Sun Jun 29, 2008 4:02 am
Location: Warsaw, Poland
Contact:

Re: Make classes this way?

Post by ramarren » Wed Apr 08, 2009 7:24 am

I meant that you could use contexts to "destructure" a class instance, like this:

Code: Select all

(defclass foo ()
  ((bar :accessor bar-of :initarg :bar)
   (baz :accessor baz-of :initarg :baz)))

(defcontext foo-context (foo-instance)
  (deflex bar (bar-of foo-instance))
  (deflex baz (baz-of foo-instance))
  (defun barbaz ()
    (+ bar baz)))

(defun test ()
  (let ((foo (make-instance 'foo :bar 2 :baz 3)))
    (with-context (foo-context foo)
      (barbaz))))
This requires typing out slot accessors, and now I see that this won't synchronize the lexical variables back into the class anyway, so it is not as close as I thought. It does avoid having to add class instance arguments to functions, though.

Post Reply