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

It would be nice to have initargs defined for protobuf classes #16

Open
eschulte opened this issue Jan 3, 2020 · 5 comments
Open

It would be nice to have initargs defined for protobuf classes #16

eschulte opened this issue Jan 3, 2020 · 5 comments

Comments

@eschulte
Copy link
Contributor

eschulte commented Jan 3, 2020

Is there a reason this isn't done currently? Thanks

@brown
Copy link
Owner

brown commented Jan 10, 2020

No good reason I think. There might be some trickiness with tracking which fields of the protobuf have been set.

eschulte added a commit to eschulte/protobuf that referenced this issue Jan 17, 2020
eschulte added a commit to eschulte/protobuf that referenced this issue Jan 17, 2020
This uses (defmethod initialize-instance :after) methods to ensure
that fields populated with :initargs are recorded into %has-bits%.

Closes issue brown#16.
@brown
Copy link
Owner

brown commented Jan 28, 2020

I had some time this weekend to experiment with your initarg patch. Thanks very much for sending it.

Figuring out what initarg handling should look like is important. A proto3 implementation should have initargs support, for instance, and it would be excellent for the initarg APIs for proto2 and proto3 to be compatible.

I found initargs to be hard to use in the presence of repeated fields. For instance, here's the existing code in the address book example to add a PERSON:

(defun add-person (&key id name email-address phone-numbers)
  (let ((address-book (read-address-book))
        (person (make-instance 'person)))
    (setf (id person) id)
    (setf (name person) (pb:string-field name))
    (when email-address
      (setf (email person) (pb:string-field email-address)))
    (loop for (type . number) in phone-numbers do
      (let ((phone-number (make-instance 'person-phone-number))
            (type (ecase type
                    (mobile +person-phone-type-mobile+)
                    (home +person-phone-type-home+)
                    (work +person-phone-type-work+))))
        (setf (number phone-number) (pb:string-field number))
        (setf (type phone-number) type)
        (vector-push-extend phone-number (phone person))))
    (vector-push-extend person (person address-book))
    (write-address-book address-book))
  (values))

Here's the same function but using initargs:

(defun add-person (&key id name email-address phone-numbers)
  (let* ((address-book (read-address-book))
         (phone (loop for (type . number) in phone-numbers
                      collect (make-instance 'person-phone-number
                                             :number (pb:string-field number)
                                             :type (ecase type
                                                     (mobile +person-phone-type-mobile+)
                                                     (home +person-phone-type-home+)
                                                     (work +person-phone-type-work+)))
                      into phones
                      finally (return (cl:make-array (length phones)
                                                     :initial-contents phones
                                                     :element-type 'person-phone-number
                                                     :adjustable t
                                                     :fill-pointer t))))
         (person (make-instance 'person
                                :id id
                                :name (pb:string-field name)
                                :phone phone)))
    (when email-address
      (setf (email person) (pb:string-field email-address)))
    (vector-push-extend person (person address-book))
    (write-address-book address-book))
  (values))

The need to initialize a repeated field with an adjustable array makes the code much longer.

Perhaps lists should be allowed as initarg arguments for repeated fields. I have to think more about how the design affects how proto3 support is implemented.

@eschulte
Copy link
Contributor Author

I see your point, although I think there are other options for the above function in which initargs seems more readable (subjectively to my eye). E.g.,

(defun add-person (&key id name email-address phone-numbers
                        &aux (address-book (read-address-book)))
  (vector-push-extend
   (apply #'make-instance 'person
          :id id
          :name (pb:string-field name)
          :phone (map 'vector (lambda (pair)
                                (destructuring-bind (type . number)
                                    (make-instance 'person-phone-number
                                      :number (pb:string-field number)
                                      :type (ecase type
                                              (mobile +person-phone-type-mobile+)
                                              (home +person-phone-type-home+)
                                              (work +person-phone-type-work+)))))
                      phone-numbers)
          (when email-address (list :email (pb:string-field email-address))))
   (person address-book))
  (write-address-book address-book)
  (values)) 

Although the above gives a vector, it is not extendable and might not satisfy the type checks on the :phone slot. I might like the idea of allowing lists to be used for repeated fields instead of vectors--at least for initialization and setf although probably not for storage. This should be doable by customizing make-instance, e.g. (if we know that's the only time we have list fields we could do):

(defmethod make-instance
    ((class (eql 'example)) &rest options &key &allow-other-keys)
  (apply #'call-next-method class
         (mapcar (lambda (opt)
                   (typecase opt
                     (list (make-array (length opt)
                                       :initial-contents opt
                                       :adjustable t
                                       :fill-pointer t))
                     (t opt)))
                 options)))

If this seems reasonable (I can see an argument that it is too fancy/unexpected) I could put together a patch to this effect.

@eschulte
Copy link
Contributor Author

Ping. Any thoughts on this?

@brown
Copy link
Owner

brown commented Mar 21, 2020 via email

eschulte added a commit to eschulte/protobuf that referenced this issue Apr 30, 2020
This uses (defmethod initialize-instance :after) methods to ensure
that fields populated with :initargs are recorded into %has-bits%.

Closes issue brown#16.
eschulte added a commit to eschulte/protobuf that referenced this issue Apr 30, 2020
This uses (defmethod initialize-instance :after) methods to ensure
that fields populated with :initargs are recorded into %has-bits%.

Closes issue brown#16.
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

2 participants