freenode/#lisp - IRC Chatlog
Search
4:00:27
beach
In a first-class global environments, I have a table called slot-readers and a table called slot-writers. These tables are indexed by symbol names.
4:00:29
Bike
in ecl and clasp direct instances of standard object use a hash table from slot names to locations.
4:01:19
beach
... then those tables work like the function entries in the first-class global environment.
4:02:22
beach
slot-value with a constant slot name gets rewritten to (funcall (car (load-time-value (find-reader-cell <name>))))
4:03:19
beach
I think SBCL must do the same, modulo the first-class global environment, and the function cell.
4:08:29
Bike
also wait, what happens if you have like (defclass foo () ((x :reader foo-x))) (defclass bar () ((x :reader bar-x))) (slot-value some-object 'x)
4:11:46
beach
In my scenario, (slot-value some-object 'x) would be translated to something like (funcall (car (load-time-value (find-reader-cell 'x))) some-object) and the cell would contain a generic function that dispatches on the class of some-object.
4:11:55
Bike
if the effective method just calls the accessor method, the discriminating function just reads from or writes to the slot directly
4:15:22
Bike
also the fast path i mentioned only happens if it's an object standard enough to not have custom slot-value-using-class methods possible. forgot that caveat
4:15:35
beach
Also, until a few years ago, I used slot-value for "internal" access, and slot accessors only for the public interface.
4:16:29
Bike
i'm not sure what the main reader method can do besides calling slot-value-using-class, i guess
4:18:17
beach
He had classes where slots were implemented as special variables so as to make it thread safe.
4:18:55
aeth
What I tend to do for internal setting is :accessor %foo :reader foo because that way I can use with-accessors on %foo without surprising behavior (i.e. only being able to set)
4:20:59
beach
So, in that case, SLOT-VALUE can not be optimized to do a direct slot access, of course, since there is no slot-location.
4:21:36
Bike
i have been thinking on and off about adding more interdependencies between methods in fastgf. for example, within a method body replace accessors with direct slot reads when possible, and then recompile the method if there's a change so it works transparently
4:21:50
Bike
i bet it would make it faster, but it would be pretty involved to maintain everything correctly
4:26:01
beach
So, I think I'll have to add SLOT-READERS and SLOT-WRITERS to the SICL first-class global environments.
4:31:02
beach
This stuff is frequently done "manually": (defgeneric foop (x) (:method (x) nil) (:method ((x foo)) t))
4:33:03
beach
Bike: Let me take a break and think about that idea. I think better when I am not at my computer.
4:56:57
beach
minion: memo for Bike: This is totally brilliant. I am convinced that it works. 1. Figure out whether any implementation does it. 2. If not, figure out what happens to the methods and call history as a result of DEFCLASS, and DEFTYPE. 3. Write it down. 4. Submit to ELS.
4:59:20
beach
minion: memo for Bike: There would be three kinds of methods: always false, always true, and requiring some action (for example if there is a DEFTYPE that expands to a compound type).
8:12:34
makomo
hah, today is the day when i finally used a full spec instead of just a symbol for ONCE-ONLY
8:30:57
jackdaniel
. is just a notation for a data structure (like #(1 2 3) is for vector), it is not a function
8:34:01
no-defun-allowed
if you do (a (. b c)) you break the expectations of depth - is it (a b . c) or (a (b . c))?
8:35:13
aeth
If it was anything else it'd probably be #.(a b) but then you wouldn't have it combined with the list representation and (a b . c) wouldn't be an obvious representation for a dotted list
8:36:56
oni-on-ion
perhaps i dont see the purpose of (a b . c) aside from being a bit strange considering lispisms
8:39:05
oni-on-ion
phoe: yeah, but its just syntax for cons isnt it? thats the point here, why its in between a and b and not prefix like everything else of lisp system
8:39:12
phoe
(x y z a . b) denotes the same thing, except three conses are consed on top of that to form an improper list, with CARs X, Y, and Z
8:39:50
no-defun-allowed
*when you're parsing, you see a paren. ooh, a list! let's see the first element. A it is. see the rest and stuff it into a cons. you see a B. see the rest again. you see...oh god a period! just return the C. cons B and C, you have (B . C). cons A to it, you finish with (A B . C)
8:40:52
ggole
Seems like confusion between reader syntax, which directs the reader to construct list structure, and the functions used to construct it
8:42:43
phoe
once, to read the cons with CAR A, once, to read the cons with CAR B, once, to read the cons with CAR C
8:43:10
phoe
. tells the reader "okay, you don't need to cons more, just stuff this thing after the period in the CDR and you're done"
8:43:26
oni-on-ion
so there is purpose to it being the syntax in the form of "(a . b)" and not "(. a b)" ?
8:45:30
oni-on-ion
ahh =) i think i get it now =) thanks. i was just trying to sleep and thought of this. quite cool actually ^_^ always really liked linked lists.
8:47:21
phoe
the concept of . is lost by the time you leave the reader and you get the read expression
8:48:21
phoe
you can only infer that there was a . somewhere by parsing the lists made up of the conses, and by noticing that some CDR is a non-null atom
8:48:56
phoe
and in this case, there's no difference between the result of the function call (CONS 'A 'B) and the result of the reader reading "(A . B)"
8:49:30
phoe
so the same result, a cons whose CAR is A and CDR is B, can come both from a #'CONS call and from the reader's operations.
8:51:36
makomo
what does everyone think of the following "problem": say i want to swap two rows of a matrix and i want a neat set of places for my interface. so i define something like (mrow mat i) which returns a fresh vector representing mat's i-th row, and ((setf mrow) vec mat i) which sets a mat's i-th (receiving a vector as a value and doing it element-wise).
8:51:39
makomo
but then, let's say i want to swap a mat's rows without creating an intermediary vector. (rotatef (mrow mat i) (mrow mat j)) would create a fresh vector since it evaluates (mrow mat j) before assigning. i could define my own (swap-row mat i j) but then that doesn't play nicely with places.
8:51:41
makomo
in general, the problem is that setf isn't "fully composable", because the expansion for a place cannot depend on the the form that produces the value to set. for example, i would like for (rotatef (mrow mat i) (mrow mat j)) to expand into a fully in-place swap of the rows (composition of smaller rotatefs). (rotatef (mrow mat i) (get-some-vec)) however would behave just as before.
8:51:43
makomo
so basically, we would have the ability to analyze the value-producing forms as well and generate expansions of places using that information.
8:55:21
makomo
i don't know whether this would make SETF better or worse. you get more composability and flexibility but perhaps SETF would become irregular, as you would have no proper separation of (1) eval the value-producing form and (2) set the place's value using its expansion and the produced value
8:58:32
makomo
so you could encode any conceptual "assignment operation" into a SETF call, and let the expander generate code that would do that work
9:00:09
makomo
phoe: yeah, but that's what the thought experiment is about. the ordering between individual assignments would still stay, but there would be no separation between the two phases of getting the value and putting the value somewhere
9:00:59
makomo
because an expansion might be able to do a better job when there is no separation (in this case, i wouldn't have to cons a new vector just to swap 2 rows)
9:01:11
phoe
theoretically you have control over SETF in form of define-setf-expander, but I think it would be somewhat confusing.
9:01:33
makomo
phoe: that's still not enough, because a setf expander doesn't have access to the value-producing form
9:01:53
phoe
for example, the expansion of (setf (subseq #(0 1 2 3) 0 2) (subseq #(5 6 7 8) 0 2)) also conses up a new vector.
9:02:18
phoe
this could be replaced by a very small and efficient loop, but SETF is too limited to be able to do that.
9:05:52
aeth
Notice that (1) the row size is in the name of the setf and (2) I use multiple values as the input
9:05:56
makomo
i thought about VALUES as well, but it won't work with "compound objects" that have a large number of "components"
9:09:26
aeth
Swapping a row is pretty simple if you're willing to put it in a function. Very elegant, actually, since you can just swap the elements via PSETF in a loop.
9:09:35
makomo
the cool thing is that this "composable setf" could also expand into further such setfs, etc.
9:10:34
makomo
aeth: true. that's what i ended up doing in the end, but it doesn't play nicely with places. also, i have NxN matrices, so i can't use PSETF
9:10:54
makomo
unless you were thinking of using PSETF for the swapping of the individual elements, in which case you can just use ROTATEF
9:13:16
makomo
now imagine a composable ROTATEF, you could swap literally anything without inducing overhead. swapping two matrix rows would just expand into multiple ROTATEFs between the individual elements
9:15:46
aeth
(let ((m (make-array '(2 4) :element-type 'single-float :initial-contents '((1f0 2f0 3f0 4f0) (5f0 6f0 7f0 8f0))))) (rotatef (array-row-of-4 m 0) (array-row-of-4 m 1)) m) => #2A((5.0 6.0 7.0 8.0) (1.0 2.0 3.0 4.0))
9:16:02
russellw
If I do (setq x 1) at the top level with no previous declaration of x, it actually works, to my surprise. What kind of variable does that create? Is it a special variable, the kind that would be created by defvar?
9:17:39
makomo
russellw: it probably does ""the right thing"" and proclaims the variables special, but i don't know for sure
9:18:16
makomo
aeth: that's a nice trick for a small number of components, but for larger Ns, you're back at consing and what not
9:18:21
aeth
makomo: https://gitlab.com/zombie-raptor/zombie-raptor/blob/b90f23cf6168f892fce8fd980649eaf882662acb/util/array.lisp
9:18:23
heisig
makomo: You could use a compiler macro for (setf mrow) that eliminates the intermediate copy if given a call to mrow as an input.
9:19:17
aeth
makomo: the consing threshold is probably higher than you think, though, and might be entirely optimized away.
9:19:58
makomo
heisig: hmm, interesting idea. but do i have the guarantee that SETF will call my setter with the value-producing as is, instead of say, evaluating it, storing it into a local variable and then passing that variable to my setter
9:20:56
|3b|
i think at least one implementation does something else though, maybe one of the commercial ones?
9:21:34
makomo
aeth: yeah, probably, but i'd still like to have a general mechanism that could handle a large number of components as well
9:22:51
aeth
(well, I set up the loop in a macro rather than do the loop at runtime... should be better for 4, probably not for 16)
9:32:23
heisig
makomo: A combination of compiler macros and a custom, composable SETF (CSETF?) might work. I will think about this, because I really like the idea of doing (crotatef (row a i) (row b j)).
9:35:11
makomo
heisig: however, if you're already "reinventing" setf, i.e. implementing your own, do you really need compiler macros then?
9:35:27
pjb
oni-on-ion: you must be careful, people write confusing things. For example, <phoe> (list* a b c d e f) ;=> (a b c d e . f) is wrong. Actually, (list* a b c d e f) -> (the-value-of-a the-value-of-b the-value-of-c the-value-of-d the-value-of-e . the-value-of-f)
9:35:39
makomo
you just write your own mechanisms of registering an "assignment expander", which takes into account both the destination and the source forms, and generates whatever
9:37:30
pjb
oni-on-ion: in lisp, things can be computed at different times: read-time, compilation-time, macro-expansion-time, load-time, run-time. And with the use of #. eval compile load-time-value and other operators, you can freely embeb one time in the other.
9:38:17
pjb
But basically, syntax, and reader macros, are read-time operations. The purpose is to obtain a lisp object that has been "read" from some characters with some syntax.
9:39:03
pjb
Once the reading has been done, the lisp object has been created, and it becomes a literal object. (it should be considered immutable).
9:39:10
russellw
Are there any pitfalls with eval that I should watch out for? I have tested it on a complex form including defstruct and defun, and disassembled a resulting compiled function, and everything seems to work just the way it would if the evaluated code had been part of the program in the first place
9:39:17
pjb
If an implementation provided operators to make immutable objects, reader macros could use them.
9:39:34
pjb
russellw: CL:EVAL doesn't take an environment argument, so it works in the global environment.
9:40:12
pjb
oni-on-ion: there's however a trap here: a reader macro can read not the final object you'd want, but instead a sexp, which when evaluated, will produce the final object!
9:41:37
pjb
#P reads a pathname. Also, this means that you cannot read eg. a logical pathname without having first defined the logical hosts. Sometimes, you'll have to use logical namestrings instead of #P.
9:42:06
pjb
On the other hand, #'foo reads the expression (CL:FUNCTION foo). This expression needs to be evaluated to obtain the function.
9:42:37
pjb
Notably, if you use #' in a literal list, it won't be evaluated, so you won't get a function in there!
9:43:26
pjb
(defparameter *my-funs* '( #'sin #'cos #'tan )) ; wrong! Also, it's confusing because the printer usually prints (CL:FUNCTION foo) as #'foo: *my-funs* #| --> (#'sin #'cos #'tan) |#
9:43:59
pjb
(defparameter *my-funs* (list #'sin #'cos #'tan )) ; you need a run-time list; then: *my-funs* #| --> (#<Compiled-function sin #x3000000AC95F> #<Compiled-function cos #x3000000AD26F> #<Compiled-function tan #x3000000AE64F>) |# this is a list of functions!
9:44:30
pjb
Of course, you can evaluate #'foo only if foo has already be defined. If not, you have to keep the function name 'foo instead of the function itself #'foo.
9:45:03
pjb
Then you can use it in a literal list: (defparameter *my-funs* '(sin cos tan)) *my-funs* #| --> (sin cos tan) |# a list of symbols, naming functions, known or future.
9:45:55
heisig
makomo: I don't know whether one needs compiler macros here. I like them because you can get the (foo x) case fast, while still allowing the (apply #'foo (list x)) case.
9:45:58
pjb
So <phoe> whereas #'cons, #'list, #'list* are functions is wrong too. #'foo is NOT a function, it's a list: (type-of (quote #'foo)) #| --> cons |#
9:46:38
pjb
#'foo EVALUATES to a function, if a function is defined with that name, or signals an error: #'foo #| ERROR: Undefined function: foo |#
9:47:49
pjb
russellw: EVAL is only good to implement a REPL or LOAD. If what you're doing could be done at the REPL, or by saving the sexps in a file and loading it, then you're good.
9:49:11
russellw
pjb, right; I'm working on a machine learning project, so if the generated code ends up working the same as it would if I had written it in a file and loaded it, this is good
9:51:30
pjb
But the machine learning program cannot call or use those new functions and types directly.
9:52:48
russellw
well, except for calling the 'do-stuff' function, where do-stuff is by convention the name of the entry point to the generated code
9:52:50
pjb
Anyways, if you generate defstructs and other types, you need to use EVAL to integrate them to be used by your generated functions.
9:53:55
pjb
For the functions you may consider just defining them as anonymous functions that you would keep in your data structure and call them from there with funcall or apply (setf (gethash (generate-function-identifier) *funcs*) (compile nil `(lambda () ,expression)))
9:54:34
pjb
If you use eval defun, you may want to intern the name of the functions in a specific package, to avoid redefining a function of the program.
9:57:09
pjb
Now for the problem of closures , you can always have something like: (let ((x 42)) (eval `(let ((x ,x)) (defun foo () x)))) ; ie. you re-create an independent closure with EVAL. Since the outer let was compiled in your program, it couldn't know that you would need it as a closure for a run-time generated function. So you can only recreate a similar closure at run-time.
9:57:53
pjb
So while eval accesses only the global environment, you can still use it to define NEW lexical environments.
10:26:30
makomo
pjb: i have a perfect macro for that, EVAL-WITH: http://plaster.tymoon.eu/view/978#978
10:31:00
makomo
phoe: because i wanted to keep the usual syntax of EVAL (it being a function and evaluating its 2nd argument), but i'm not sure why i put a &body there
10:43:05
beach
scymtym: Do you happen to know whether SBCL optimizes TYPEP with a constant atomic type specifier in a way similar to what Bike described?
11:07:12
makomo
but it's a weird macro nonetheless. it kinda looks like a LET, except that the 2nd argument is evaluated to produce a body... for EVAL to eval
11:08:08
pjb
(funcall (let ((a 1) (b 2)) (eval-with (a b) (lambda () (list (incf a) (incf b)))))) #| --> (2 3) |# would be a more typical usage.
11:08:21
phoe
EVAL should not be used unless absolutely required - if you absolutely need to create and evaluate Lisp forms at runtime
11:08:40
pjb
(loop :with fun := (let ((a 1) (b 2)) (eval-with (a b) (lambda () (list (incf a) (incf b))))) :repeat 3 :collect (funcall fun)) #| --> ((2 3) (3 4) (4 5)) |#
11:08:44
makomo
oh, this isn't for anything specific, it's just an old macro i wrote. pjb mentioned a use case above so i thought of it
11:09:35
pjb
phoe: actually, it would be more like: (loop :with fun := (let ((a 1) (b 2)) (eval-with (a b) `(lambda () ,(progn (write-line "enter a sexp using a and b:") (read))))) :repeat 3 :collect (funcall fun))
11:10:35
makomo
right, so it doesn't really make sense for EVAL-WITH to not evaluate the 2nd argument
11:10:39
pjb
My example above with lambda, since it's not quoted, actually refers to the variables in the compilation-time closure!
14:02:14
beach
didi: In combination with WITHOUT-ERRORS it can be used to write a very simple function for determining whether a list is a proper list.
14:05:05
didi
I'm yet to use an improper list. I can imagine there's an use case for circular lists, but for what would one use a dotted list?
14:05:46
pjb
didi: actually, once I had a case, were I didn't want NIL as the end of the list. IIRC, I had to distinguish two cases for the end of the list.
14:06:09
pjb
But otherwise, it's more often an error, indeed. Hence the use of ENDP and list-length.
14:06:47
pjb
notably, length may do an infinite loop on circular lists, so if you have them, better use list-length (or my list-lengths which gives more info).
14:16:58
jcowan
didi: my implementation of lazy sequences uses improper lists to represent an incompletely materialized sequence, with the generator function in the cdr of the last pair.
14:17:48
jcowan
you can cdr down the already realized values, and when you get to a pair whose cdr is nil then you are done, but if you get a function then you call it and add a new pair.