libera/#commonlisp - IRC Chatlog
Search
2:33:04
ck_
Guest74: only briefly, so I don't get slapped with the off-topic parenthesis, I'll say that for most of these very ritualistic languages, tooling probably plays a major role; it's why "the IDE" is so important there
5:35:07
contrapunctus
I saw the `<slot>-of` convention for accessors on Cliki yesterday, and was thinking of using it...any reason not to?
5:38:21
mfiano
There was a lengthy discussion about this yesterday in #clschool that I really don't want to repeat, but the short answer is, don't. Always %-prefix your slot names, and never export %-prefixed symbols.
5:45:30
contrapunctus
I've noticed the %slot convention, but don't understand the reason for it. Is it to act as a warning sign for code depending on implementation details?
5:45:47
pranavats
I hadn't thought of %-prefixing my slot names. That might solve some name clash issues for me.
5:45:53
Nilby
but people who like to follow opinionated style pronouncements probably won't be using lisp in the first place
5:46:56
mfiano
contrapunctus: The rationale is simple. Slots are implementation details. They should _never_ be exposed to consumers of your code.
5:47:34
mfiano
By %-prefixing slot names and remembering never to export % prefixed symbols, you are protecting yourself and your users from bypassing protocols.
5:48:32
mfiano
Also, every experienced Lisper knows that %-prefixed symbols already means dangerous, or low-level.
5:49:58
mfiano
It is the most important style convention in my opinion. Unlike most other languages, constructs such as accessors, classes, etc are not exported, but _symbols_ are.
5:52:58
contrapunctus
I've heard about never exposing slots to consumers, and I can see how it makes code brittle, but so far I've accessed slots directly all the same...mainly because `with-slots` is much more concise than `with-accessors` x-P
5:53:49
mfiano
contrapunctus: That is a problem if your users want to extend an accessor that sets a slot value in their code, by adding an auxillary method.
5:54:16
mfiano
and if you don't want them to, you don't export that accessor, and add another %-prefixed accessor for your own internal use.
5:55:38
pillton
You can also specify more than one reader, writer and/or accessor e.g. ((name :initarg :name :reader name :accessor %name)).
5:58:31
contrapunctus
I'm talking the increased verbosity of `(with-accessors ((a a) (b b) (c c)) obj ...)` vs `(with-slots (a b c) obj ...)` ...but I suppose it's no worse than `let`.
5:59:06
mfiano
contrapunctus: My recommendation is to prefer explicitness over with-slots/with-accessor/symbol-macrolet. It is easier to understand, and you don't run into performance traps by eg; evaluating an expensive accessor more than once.
6:03:06
mfiano
Indeed I am. Considering an accessor can be any arbitrary method that performs any amount of work as part of its logic, using such a thing would result in redundant computations, which is a problem both for performance reasons, and that of side-effects.
6:03:53
mfiano
But moreso because (accessor foo) just reads better than some symbol that you have to scan backwards to see how it was defined and its value mutated along the way.
6:07:45
mfiano
I'm not saying symbol macros (which includes with-slots and with-accessors), but their utility is limited in my opinionated idea of good code.
6:12:48
mfiano
If you don't know what they are, don't worry; you're not missing much. Just read Let Over Lambda some time.
6:13:41
mfiano
That book is very much a "hey, look what macros can do" rather than a practical book.
6:18:52
beach
contrapunctus: WITH-ACCESSORS rarely have entries like (a a) (b b). More likely it will be (a some-package-prefix:a) (b some-package-prefix:b).
6:19:23
mfiano
Sure. Like most Lispers, I have different advice on good style than others. I would suggest you not to blindly apply this style to your code, but to think of why it is preferred by me (and some others)
6:22:57
pjb
contrapunctus: consider: (defpackage "POO" (:use "CL") (:export "KOO" "SOO")) (in-package "POO") (defclass koo () ((soo :initarg :soo :accessor soo)))
6:23:23
pjb
then since soo is exported, a client can write: (setf (slot-value (make-instance 'koo) 'soo) 42)
6:24:13
pjb
accessing the slot directly! But perhaps you have additionnal methods on the accessor soo to validate or synchronize the the slot. With (setf slot-value) those methods wouldn't be called, and the client would have broken your class.
6:24:32
pjb
contrapunctus: instead with: (defpackage "POO" (:use "CL") (:export "KOO" "SOO")) (in-package "POO") (defclass koo () ((%soo :initarg :soo :accessor soo)))
6:24:32
beach
contrapunctus: The -of convention suggests that it accesses a slot. But you want to do everything you can to prevent a slot from being even suggested. So this convention has the same problem as the get- and set- convention in other languages. You want to leave the flexibility of changing the implementation from accessing a slot to computing the value, or vice versa.
6:25:12
beach
contrapunctus: Or else, you would have to name all the functions in your protocol with the -of convention, which would be truly silly.
6:25:25
pjb
(he can stil use it, but now he must write: (setf (slot-value (make-instance 'koo) poo::%soo) 42), with both :: and % clear markers that he shouldn't.
6:25:54
pjb
In any case, the reader of the source will know that something iffy is occuring with this ::%
6:27:25
beach
pjb: Though if you saw the discussion in #clschool yesterday, you know that if you have a protocol package that contains only exported symbols, then the implementation package need not have any exported symbols at all, so no need for the % convention.
6:28:22
pjb
beach: exactly, but we still have two different symbols POOapi:SOO and POOimplementation::SOO
6:29:09
beach
contrapunctus: Imagine you have DATE-OF-BIRTH-OF as a slot reader, but you have AGE as a function that computes the age from the date of birth and today's date. Now if you decide to cache that computation, do you then change the name of the protocol function?
6:43:59
contrapunctus
pjb: yeah, I get the rationale, I just haven't had any logic to put in accessors yet. Probably because most of the projects where I use CLOS are still in early stages. As I said, I'll probably remove the exporting of slots, sometime.
7:11:14
jackdaniel
if we assume, that using slot-value is bad™, then it doesn't matter whether the slot name is the same as the reader or not
7:24:45
jackdaniel
I don't have a strong opinion about <slot>-of, but it is worth noting that there are two types of slots: caching some computation, and storing some value; -of convention may (or may not) be a hint to the code reader with this regard
7:37:27
jackdaniel
why the client shouldn't know what is the (semantic) role of yhe accessor? this does
7:37:57
beach
As I explained, client code should not need to know whether something is stored or computed.
7:39:12
beach
It is not about semantics. Of course, client code should know the semantics. It's about the implementation details that make the semantics work. And client code should not have to know about implementation details.
7:41:24
beach
Here, you have an example where the semantics of AGE can be realized in (at least) two different ways.
7:41:36
Nilby
(o:elt (make-instance 'foo :bar 1 :baz 2) :baz) ⇒ 2 or even (o:elt (make-instance 'foo :bar 1 :baz 2) 1) ⇒ 2
7:46:16
jackdaniel
DATE-OF-BIRTH-OF vs AGE touches what mfiano suggested - he does use full accessors to make it apparent that an expensive computation may happen
7:46:42
jackdaniel
having DATE-OF-BIRTH-OF suggests, that calling this function is cheap, because it is just a storage (with some optional auxiliary functions)
7:47:36
beach
So you would advocate changing the name from AGE to AGE-OF if the maintainer decides that it may be advantageous to cache the result?
7:48:16
jackdaniel
age is a matter of computation (because it changes with time), while date of birth is the object property
7:50:07
Nilby
one might have (age person) "Ages ‘person’ by one Plank unit." and (age-of person) "Return the age of ‘person’ in Plank units."
7:51:27
jackdaniel
beach: I agree; and as I said I don't have a strong opinion; but in some clear cases it /may/ be a useful convention
8:00:59
Nilby
I'm not sure why any of this is issue with a language where we can programaticly re-map any accessor/slot in any package in any way, probably without runtime overhead. We have the best practical API compatibility of nearly any language. A bigger issue seems: did the programmer even bother to document what anything does.
8:03:07
Nilby
i love, easy to read, unsurprising, internally consitent code. but it doesn't have to obey any strict conventions.
8:25:56
Nilby
I'm good with the convention that every function returns a value and every value is lisp obejct. And using one natural language increases my small brained code comprehension (나선형-우주 x)
8:58:51
pjb
jackdaniel: it doesn't matter. The object could instead store the age and the date-of-the-day and compute the date-of-birth.
8:59:19
pjb
jackdaniel: the point is that for modularity, it's got to let the implementation change without changing the API.
8:59:21
jackdaniel
I believe that you miss the point of what I've wnated to convey; probably that's my fault
9:01:39
pjb
That said, personnally, I'm not in favor of strong conventions. They depend on the circumstances and projects, and circumstances and projects can change.
12:25:53
Guest74
The one I really don't understand is the not exporting slots. But, are we really exporting slots? I thought it was all symbols? Maybe those symbols define my protocol? Isn't it only a current implementation detail that they are slots? If I specifiy my protocol and never mention slots, do I need to safeguard against somebody coming along and
12:28:00
pjb
Guest74: indeed, only symbols are exported. But slots are named by symbols. So if you export symbols naming slots, you give access to the client (the code that uses your package) to the slots that should be a private implementation detail.
12:28:36
pjb
Guest74: it should be sufficient to tell the client not to use slot-value on your objects.
12:29:01
pjb
Guest74: but not everybody read the documentation, so in big projects, with lots of people, it is nice to make it harder to use slot-value on your classes.
12:29:36
pjb
Guest74: the problem is that CL is not into BDSM: it doesn't prevent anybody to use anything.
12:31:38
Guest74
i prefer not to jump through hoops to prevent someone else from doing things not as intended. But that's just my take. I'm not big into dictating what others should do.
12:39:06
flip214
You can't (practically) prohibit anyone having code in the same process as your library from getting the address and using FFI to access the low-level data, anyway. So having some kind of protocol should be good enough.
12:44:34
beach
Guest74: I know that Xach (among others I guess) considers any use of SLOT-VALUE and related functions to be "forbidden" by client code. And, yes, if your clients are well trained, that should be enough. The % convention is just a reminder for them, since (as people pointed out) with modern Common Lisp systems you can't really prevent anything.
12:46:15
beach
Guest74: You can also use the structure where the protocol package contains only its exported symbols, as I outlined above. That's a very nice organization, and it prevents any use of SLOT-VALUE automatically, without requiring any naming conventions like %.
12:49:48
Guest74
Sure, I have that for quite a few protocols, most of mine there is no concrete object anyways, which is the purpose of the protocol..
12:51:03
beach
Interesting! Where did you learn that structure? And how do you then define your implementation packages?
12:51:59
beach
I am asking because I know of only CLIM as being a system that requires this structure.
12:52:50
pjb
Guest74: sure. It's a matter of context. Size of the project, number of programmers, who will use it, etc.
12:53:39
Guest74
I can't say I learned it from programming. It just makes sense the way my brain works. Implementations are just methods on the protocol functions. I'm not sure what else you mean?
12:53:46
pjb
flip214: if you build a controlled environment which sicp with it's first time environment will allow and make it usable, you can remove FFI and thus prevent low-level access and such security bugs.
12:54:35
beach
Guest74: If you put the DEFGENERIC in the protocol package, then the protocol package will contain not only exported symbols, but also names of parameters and other noise.
12:55:20
beach
Guest74: Like I said, the proposed structure is to have the protocol package contain only exported protocol symbols, so no internal symbols.
12:55:51
hayley
I wonder if anyone would notice if their "FFI" solution involved compiling C to Lisp code.
12:56:24
Guest74
I'm not sure what the big deal is about args? Most of them are just the name of the protocol anyways(at least in my case)
12:56:27
pjb
hayley: if you compile C to lisp, then you can do ffi staying in the lisp controlled environment: C programs will be safe!
12:57:07
beach
Guest74: It is not a big deal, but you said you often use the structure I suggested, and if you put the DEFGENERIC form in the protocol package, you do not use that structure.
12:57:21
hayley
Else (for some other language) I would have to write a guide like "How to do FFI in language: 1. You don't"
12:57:53
beach
Guest74: So I was asking where you learned the structure where the DEFGENERIC forms are not written in the protocol package, because that structure is highly unusual.
13:06:05
hayley
We have the Vacietis compiler which compiles C to Common Lisp, so I wonder how the compiler fares compiling a SSL library. CL+SSL is the only FFI library I use routinely, so I would basically never have to touch FFI again if this idea works.
13:09:00
hayley
Sure, though I wouldn't mind if the generated CL used Common Lisp objects rather than an octet vector for memory. I believe Iota uses the latter, which is still better for compatibility.
13:11:08
Guest74
Beach: Perhaps I misunderstood you, but you said contains only its exported symbols and I must have easily confused that with only exports symbols naming the protocol, which is the only thing I do intentionally. now would not having these args in the package hinder debugging? ...seems something keeps kicking me off the network.
13:11:12
froggey
iota is definitely a crude hack, but definitely designed to be fairly compatible with existing C code
13:11:23
hayley
So I'd prefer the Zeta-C-ish approach that Vacietis uses, I think. But if you say it's a hack, I'll see how it fairs.
13:13:13
beach
Guest74: I see. Debugging would be done by the author of the module, who obviously uses internal symbols in the module, so I see no impact on debugging.
13:13:15
hayley
I also have to wonder how much software written in C breaks in practise, if you use bignums for char, int, long, etc. It would help if I could pick a MAX_INT perhaps.
13:13:45
froggey
something that does away with the big byte vector for memory would be nice, but gets surprisingly close to the pointer provenance problem
13:14:14
froggey
I thought about it a bit, but always ended up with a fairly exotic memory model which I'd guess most C code won't like
13:16:16
Nilby
hayley: There's quite a lot C software that relies on integer size, wrapping, truncation, etc. Maybe more than not.
13:39:57
lisp123
I was speaking to one of my old friends after many years and I mentioned I started using CL
13:40:29
Nilby
dlowe: Vacietis has a fair bit of a libc in CL, iota too has a bit of a Mezzano specific libc
13:43:41
Guest74
beach: So you write the generics in a package, then create a separate package just to import/export the symbols naming the generic functions? I'm guessing maybe mcclim is doing that because it both creates and implements the protocol in the same space?
13:46:03
jackdaniel
Guest74: we are a bit smarter, the implementation package "uses" the protocol package
13:47:27
froggey
Nilby: the iota libc is mostly portable common lisp. though it needs nibbles and some float stuff to actually work, but there are portability libraries for that
13:48:56
Nilby
froggey: Thanks. Now that I'm looking it might help me finish my Mezzano portability layer
13:49:26
beach
Guest74: No. The package system allows you to do thing the other way around. You write your package definition (say) (defpackage #:api ... (:export #:fun1 #:fun2)).
13:50:17
beach
Guest74: Then you write your implementation.lisp file like this (cl:in-package #:api-implementation) (defgeneric api:fun1 (...)).
14:00:14
Guest74
beach: do you have a specific example to look at? Did you really mean defgeneric and not defmethod in your example? This constant kicking isn't helpful.
14:03:03
Guest74
So implementation doesn't mean implementation of the protocol, but definition of it?
14:04:03
Guest74
any code that defines it? or that actually implements it as in has defmethod etc...