Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple changes single copy #8

Open
charJe opened this issue Mar 8, 2023 · 6 comments
Open

Multiple changes single copy #8

charJe opened this issue Mar 8, 2023 · 6 comments

Comments

@charJe
Copy link

charJe commented Mar 8, 2023

What do you think about something like say pmodf (like psetf)? On the inside, it would be guaranteed that the object would only be copied once.

Something like this.

 (defstruct a b c)

(let ((a (make-a :b 1 :c 2)))
  (pmodf (a-b a) 2
         (a-c a) 3))
@smithzvk
Copy link
Owner

smithzvk commented Mar 9, 2023 via email

@charJe
Copy link
Author

charJe commented Apr 7, 2023

I think the optimizations are the main reason I'm interested in this. For example with an clos object (more general than a list) modf-ing two slots will result in twice the wasted memory than the equivalent proposed pmodf.

@githubforced2fabad
Copy link

githubforced2fabad commented Apr 28, 2024

Hello again, charje here (I got locked out of my account). After playing with modf a bit more I think I realized that the modf macro already supports what I want. I am currently only using modf for clos objects. The thing I'm talking about is the more parameter like so:

(let ((a (list 1 2 3)))
  (modf (first a) 4
        a (second a) 5))

The above returns (4 5 3), and correct me if I'm wrong, but it will only create 1 new list and mutate the first and second elements. (I think the new list will still tail-share the (3) though.)

Assume we have a clos object.

(defclass person ()
  ((hair-color :accessor hair-color :initarg :hair-color)
   (age :accessor age :initarg :age)
   (id :accessor id :initarg :id)))

Is there a way (or would you be open to something like Haskell record-change syntax? Here is an example of what I mean using an imaginary modf-writers macro for lack of a better name:

(let ((james (make-instance 'person :hair-color :yellow :age 4 :id 1)))
  (modf-writers james
    hair-color :brown
    age 9))

The above would be equivalent to the following:

(let ((james (make-instance 'person :hair-color yellow :age 4 :id 1)))
  (modf (hair-color james) :brown 
        james (age james) 9))

@githubforced2fabad
Copy link

Another candidate for the name could be writef.

@githubforced2fabad
Copy link

Do you have any thoughts about this?

@smithzvk
Copy link
Owner

smithzvk commented May 7, 2024

Hey, sorry for the delay. I put off replying until I could look into it and lost track of this.

Yes, you are correct that in the list example you provided the lists will share the tail '(3).

I looked at the way Haskell deals with this and I don't think that fits well within the normal MODF macro. It makes sense to create a new construct for this as you suggest. I would think something along the lines of COPY-WITH-CHANGES (WRITEF feels like it should be writing to a file or something like that) which takes an object instance and the rest of the argument list is taken as keyword initializers for object slots. Something like this:

(defun copy-with-changes (obj &rest changes)
  #+ecl
  (error "No ECL implementation")
  #-ecl
  (let* ((class (class-of obj))
         (slot-groups (mapcar #'closer-mop:class-direct-slots
                              (closer-mop:class-precedence-list class)))
         (new-instance (apply 'make-instance class changes))
         (specified-args (let ((ht (make-hash-table)))
                           (iter (for (ia val . rest) on changes by #'cddr)
                             (setf (gethash ia ht) t))
                           ht))
         encounted-slots)
    (break)
    (iter
      (for slots in slot-groups)
      (iter
        (for slot in slots)
        (unless (or (member slot encounted-slots)
                    (some (lambda (x) (gethash x specified-args)) (closer-mop:slot-definition-initargs slot)))
          (cond ((slot-boundp obj (closer-mop:slot-definition-name slot))
                 (setf (slot-value new-instance (closer-mop:slot-definition-name
                                                 slot))
                       (slot-value obj (closer-mop:slot-definition-name slot))))
                (t (slot-makunbound
                    new-instance
                    (closer-mop:slot-definition-name slot)))))
        (push slot encounted-slots)))
    new-instance))

You'd use it like this:

(let ((james (make-instance 'person :hair-color :yellow :age 4 :id 1)))
  (copy-with-changes james :hair-color :brown :age 9))

You'd have to extend it to handle structs (and ECL and maybe other implementations out there... I haven't kept up).

That said... we must keep in mind that this is all just a hack when using this library that anything to do with CLOS, or arrays, or hash-tables or any other object that doesn't map nicely onto a functional paradigm. If you have a CLOS object (or array/hash-table/what-have-you) that has is small but holds a few large objects, this may still be a nice way to handle it as everything is shallow copied and performance may not be too bad.

Patches more than welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants