emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Emacs-diffs] trunk r116995: cl-lib defstruct introspection


From: Daniel Colascione
Subject: Re: [Emacs-diffs] trunk r116995: cl-lib defstruct introspection
Date: Mon, 21 Apr 2014 15:26:21 -0700
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Thunderbird/24.4.0

On 04/21/2014 03:03 PM, Stefan Monnier wrote:
>> That's new. Using the whole ChaneLog message has been a recommendation,
>> but never a requirement.
> 
> For `elpa', that's true, but for `emacs' it's always been a requirement,
> on the premise that this should/will allow us to drop the ChangeLog
> files at some point.

Thanks for the clarification.

>> Now there's one more step on the commit path, and a useless one at
>> that: the changelog entry is available in the change itself and in the
>> message to the mailing list.
> 
> C-x v v can copy the message from ChangeLog for you (and set Author:
> and Fixes: at the same time), so it's not so bad.

Can it? I tried it in vc-dir and got a completely unrelated ChangeLog hunk.

> The way to fix this, is to make ChangeLog unneeded.  First step on this
> path is to provide some way to make `C-x 4 a' usable without ChangeLog.
> 
>>>> +The @code{cl-defstruct} package also provides a few structure
>>>> +introspection functions.
>>> I'm curious: when/where did you bump against a need for that?
>> I have a few private macros that lexically bind structure slots,
> 
> Which part makes it impossible/impractical to use standard accessors for
> that?

:conc-name, for starters. Also, :read-only, although you could argue
that you shouldn't go around mutating read-only slots anyway.

>> and this information is also needed for some interface-generation work
>> I'm thinking of doing.
> 
> Not sure what "interface-generation" means, but it sounds interesting.

I'm just playing around at this point --- but the basic idea is that we
need some way to connect independent Emacs components, and this
mechanism should be more structured than some kind of
do-everything-in-a-giant-cond handler function, but much lighter and
more comprehensible than EIEIO.

One approach is to define COM-like (wait! keep reading!) "interface"
structures that bundle useful functions into composeable pieces, then
have Emacs components interact in terms of these interfaces. We can make
them very lightweight, and let all interfaces inherit from a common
interface that lets callers ask for additional functionality:

(iface-declare (iface-base nil)
  "Interface from which all others inherit."
  ((iface-query instance interface)
   "Get an implementation of INTERFACE from INSTANCE."))

For example, we could define some project interfaces like this:

(iface-declare (project iface-base)
  ((project-get-root-directory project )
   "Find the root directory of PROJECT.")
  ((project-get project property)
   "Get a project property PROPERTY.")
  ((project-put project property value)
   "Set a project property PROPERTY to VALUE."))

(iface-declare (project-c iface-base)
  ((project-c-include-directories project file)
   "Return a list of include directories for FILE in PROJECT.
FILE is a fully-qualified name."))

iface-declare defines trivial wrapper functions that make it convenient
to call through interface fields. So the generated definition of
project-get, for example, would look like this:

(defun project-get (inst &rest xargs)
    "Get a project property PROPERTY."
    (declare (advertised-calling-convention (project property))
    (apply (cl-struct-slot-value 'project 'get inst) xargs))

If you have a project and want to see whether it supports finding C
include paths, you'd just use iface-query with 'project-c, and if that
succeeds, call (project-c-include-directories my-c-project file).

You'd actually *make* interface instances by building an interface
struct as you would any other struct and supply closures (e.g., the
result of `apply-partially') for the slot values so that you can
maintain state (or not) between subsequent calls on the same interface
instance.

Anyway, all of this would be very lightweight and (I think) resolvable
completely at compile time.

Note that it's probably not a good idea to go even simpler and have,
say, my-git-project inherit from project directly: what if the size of
project changes? (Things would break because cl-defstruct accessors
hardcode field offsets, and that's a good thing.) What if you want to
implement multiple pieces of optional functionality? (Sure, you can make
some struct fields nullable, but then you have to provide some way to
query whether a field is actually implemented.)

Better to just define additional interfaces for optional functionality.
The interface approach seems decently light and extensible, and the
implementation complexity is low.

>>>> address@hidden cl-struct-set-slot-value struct-type slot-name inst value
>>> We don't need this, since we can always use setf instead.
>> So? We have both (setf (aref ...) ...) and (aset ...).
> 
> That's only because (setf (aref ...) ...) needs to macroexpand to something.
> [ It's one of the differences between Common-Lisp and Elisp.  ]
> 
> In your case, (setf (cl-struct-slot-value ...) ...) can macroexpand to
> something without needing cl-struct-set-slot-value.  Actually, in order
> for (incf (cl-struct-slot-value ...)) not to compute the offset twice,
> (setf (cl-struct-slot-value ...) ...) will end up expanding to something
> else than a call to cl-struct-set-slot-value.
> 
>> That test was there in cl-check-type. The test doesn't make sense to me
>> either. We should drop it in both places if we drop it in cl-the.
> 
> Great, let's drop it then.  Thanks.

Will do. That test never made much sense to me anyway.

>>>> +(cl-define-compiler-macro cl-struct-slot-value
>>> Please use (declare (compiler-macro ..)).
>> Why? In both cases, the compiler macro is written out-of-line and in
>> both cases, we just stick the compiler macro on the symbol's plist.
> 
> Because that's the style we use in Elisp.

Well, recently. ;-) It wasn't too long ago that the style was to
let-bind dozens of variables at the top of huge functions and setq them
everywhere.

> Note that (declare (compiler-macro ..)) can provide the compiler-macro
> "inline" or "out-of-line".

Sure, but it'll be really ugly if it's inline and relatively large (as
the macros here are). It goes with a trend I've noticed in the APIs
you've implemented, though: you seem to prefer having people defun
regular functions and combine them in interesting ways (e.g., with
advice-add) instead of using top-level definition forms directly (e.g.,
defadvice). I largely agree with your approach.

> 
>>> I guess this goes back to the earlier question about when/where the use
>>> for this functionality came up.
>> Unless we're using this functionality in generated code where, while the
>> slot is constant, it's more convenient to use that slot's name than to
>> try to determine the accessor name.
> 
> Ah, so it's for code generated based on cl-struct-slot-info?
> Right, that makes sense.

Attachment: signature.asc
Description: OpenPGP digital signature


reply via email to

[Prev in Thread] Current Thread [Next in Thread]