libera/#commonlisp - IRC Chatlog
Search
8:05:29
Tallund
Hi, I'd recently tried to implement type generics in custom collections defined by defstruct via macro. The struct would be created with a read only slot of type 'symbol' to act as a type specifier that would be read out at macro expansion time into a backquoted declaration form preceding collection operations. And I did succeed, actually- for
8:05:29
Tallund
values known at compile time. It fails as soon as I call the function-like-macro on a collection bound to a fresh let form within the REPL. I think I've read something about runtime type declarations somewhere and started trying to figure that out with a type specifier symbol retrieved only then but I've been having trouble finding anything about
8:05:30
Tallund
the evaluation semantics for the different declaration forms. Am I wasting my time here? Is there a better way to do this? Is there maybe some quicklisp package that implements generics I didn't find in the curated list?
8:09:21
flip214
I seem to remember that clouseau (or was it something in McClim?) would show a graphic of a CLOS class hierarchy, but I can't find that any more. Any hints, please? Thanks a lot!
8:10:43
beach
flip214: The CLIM listener can do that. I believe it has a "," command for that, like ,show class subclasses or something similar.
8:12:31
beach
Tallund: But it is hard to say what is going wrong since you are not showing your code.
8:13:33
Tallund
I'm a bit embarrassed to share it but I'll do so in a moment after testing it again.
8:13:34
beach
Tallund: I don't know what that means, but I was telling you about "the evaluation semantics for the different declaration forms".
8:16:51
beach
Tallund: But maybe you are asking about the semantics about type declarations in general. The answer is that the implementation is free to ignore them.
8:17:34
Tallund
I had Java's varying List<>s in mind when I said that, which could be specified to be of type 'List<String>' for example, and Java would then do its' typical compile time type checking upon insertion of elements into the specific list object and the retrieval of elements from the same object
8:17:35
beach
Tallund: The standard says that a type declaration is a promise from the programmer, and the implementation may or may not check it.
8:19:27
Tallund
That List<> bit I mentioned would be an example of collection type generics, at least as far as I know
8:19:49
beach
Tallund: Trying to make Common Lisp behave like a statically typed programming language is usually a bad idea in the first place. For example, there is no Common Lisp declaration that will declare the type of every element of a list.
8:22:09
Tallund
My thought was just to automatically generate the declarations from a type specifier given in one place, specified as a struct's slot value, instead of having to repeat the everywhere manually
8:23:55
jackdaniel
as a side note, in clim a presentation type sequence may have the element type specified
8:29:04
beach
Tallund: If you want to make sure the implementation checks your types, you need to use CHECK-TYPE or something similar. A type declaration is not guaranteed to work.
8:30:35
Tallund
I was mostly looking to leverage SBCL's type inference as far as I could get it to for producing macro expansion time declarations
8:31:39
Tallund
Type inference is the wrong term here, since I'm trying to fetch a value, it would be a sort of value inference
8:39:42
beach
Since declarations are not forms, you can't turn an element of a vector into the type supplied to a type declaration, unless that vector is known at compile time.
8:41:06
beach
And, again, even if you could, the implementation would not be guaranteed to check the type. For SBCL, I think it depends on the OPTIMIZE settings whether it does that or not.
8:42:18
Tallund
Ok, ok, ok, I think I'd need straight up reflection and macros all over the damn place in order to achieve the kind of value inference I had in mind.
8:44:07
beach
But you can store the type specifier in an element of a vector and then use TYPEP at run time.
8:44:14
Tallund
No, but I think I can manually implement some value inference via compile time form evaluation in macros.
8:45:43
Tallund
The ugliest solution I have in mind is just putting a bunch of symbols to act as type specifiers into a hash map and then I fetch the correct type specifier where it is needed
8:49:16
beach
Tallund: What is the advantage of having a symbol act as a type specifier, rather than using the type specifier itself?
8:49:47
_death
like (define-typed-stack single-float-stack single-float) that can generate the constructors and operators
8:51:11
Tallund
I think it'd be more than that, but I don't have enough intuition or knowledge to contest that statement
8:52:02
Tallund
beach: From my understanding and experimentation, type specifiers are a subset of symbols that themselves name a type, and not in the sense that symbols acting as variable names do
8:52:04
_death
(the checks may or may not be there; in sbcl, you may get some of them by its type inference, supposing the push operation is inlined)
8:53:07
Tallund
t, the symbol that is treated as the boolean value of true, also names the type T, as in, the object hiearchy root
8:55:48
Tallund
> What is the advantage of having a symbol act as a type specifier, rather than using the type specifier itself?
8:55:49
Tallund
I think I was hoping for SBCL to spit out optimized code via some kind of dynamic just in time compilation+ optimization, and with type checks
8:57:06
Tallund
Really, you should've figured out I don't know where the hell God put me once I started asking about 'evaluating declaration forms at runtime with type specifiers retrieved at runtime'
8:58:57
beach
Well, I kind of did, in that I pointed out that declarations are not forms, so they are not evaluated.
9:03:30
_death
note that compilation may be expensive, so you need to consider the tradeoff and proably make good use of caches
9:10:31
Tallund
I agree. As much as it bothers me, it would make the most sense I think if I then packaged the jit'd functions for the struct, in the struct, as a primitive pseudo object
9:13:22
flip214
set-pprint-dispatch says it's using EQUAL for type comparison -- so I can't use a base class and a generic function, I'd have to register each subclass as well.
9:14:06
flip214
Is there another mechanism that makes this easier? (Yeah, I can loop over the subclasses known at compile time, but still...)
9:14:50
_death
I think you can.. the paragraph is about determining whether there's an existing entry associated with it
9:26:03
scymtym
flip214: when you inspect a class in clouseau, expanding the sub- and superclasses "places" should display graphs. the context menu on those values can be used to toggle between list and graph display
9:32:45
pjb
Tallund: there are three ways to do it: (EVAL lambda-expression) (COERCE lambda-expression 'function) and (COMPILE nil lambda-expression)
9:33:49
pjb
Tallund: EVAL could produce an interpreted function, COMPILE must produce a compiled-function, COERCE could do what's best (fastest?) for the implementation. You also have to take into account the number of times you need to produce a function, vs. the number of times you will call it.
10:24:43
flip214
scymtym: ah yeah, right! clim has a left-to-right layout which works better with the class names, though -- top-to-bottom becomes too wide.
10:35:43
flip214
If my package names use "/" in their names ("foo/data", "foo/impl", ...), how would I escape that in a FORMAT call? (format nil "~/foo/data:my-format/" x) doesn't work, neither does \\/ ...
10:37:54
yitzi
flip214: forward slash isn't a character that needs escaping. `~/` is a format specifier that calls a function. http://clhs.lisp.se/Body/22_ced.htm
10:40:03
flip214
yitzi: my function needs a package (else CL-USER is used!), and my package name contains a #\/. But a / means "end of function name"...
10:43:17
yitzi
You can't do that. The spec says the function name can't have / in it. http://clhs.lisp.se/Body/22_ced.htm
11:39:37
scymtym
flip214: i think that depends, a horizontal layout would roughly be class-name-width * graph-depth wide while a vertical layout should be something like class-name-width * max-node-degree. so a horizontal layout would be good for a shallow hierarchy with lots of mixins or superclasses in general
11:40:30
dbotton
is there a "good" way to add to .asd files the ability to do a git clone (and when needed a git pull)?
11:41:38
flip214
dbotton: https://asdf.common-lisp.dev/asdf/Creating-new-operations.html add a :git-pull operation?
12:05:01
dim
I'd say thanks to Quicklisp it's not needed that much; in between releases I would simply git clone/pull into quicklisp/local-projects though
12:43:52
pjb
flip214: (defpackage "FMT" (:use) (:import-from "FOO/DATA" "MY-FORMAT") (:export "MY-FORMAT")) (format t "~/fmt:my-format/" arg)
12:44:52
pjb
flip214: and if your function itself has a bad name: (defun fmt:my-format (&rest args) (apply #'foo/data:my/bad/name args))
16:51:58
dbotton
Is there a way to check if a directory exists without creating it? (only found ensure-directories-exist)
19:12:06
nij-
Anyone knows how CFFI works? I mean, lisp has to talk to C in some way. Which channel do they use? Do they use bsd sockets?
19:14:04
Bike
cffi is not a communication between processes. it is a way for lisp code to run c code and vice versa, all in the same process.
19:14:31
Bike
lisp functions can call c functions similarly to how they would call other lisp functions, and the reverse can be done through callbacks.
19:17:37
aeth
you could always try writing a CFFI hello world and running DISASSEMBLE on the function to see what it's doing
19:18:57
pjb
or rather, use macroexpand: (macroexpand-1 '(cffi:defcfun clock :int)) #| --> (progn nil (defun clock nil (cffi-sys:%foreign-funcall "clock" (:int) :convention :cdecl :library :default))) ; t |# (macroexpand-1 ' (cffi-sys:%foreign-funcall "clock" (:int) :convention :cdecl :library :default)) #| --> (ccl:external-call "_clock" :signed-int) ; t |#
19:23:50
aeth
which is faster depends on the implementation and if it's helpful enough to comment or otherwise make known that it's calling, in this case, ccl:external-call
19:35:12
Bike
it's all running on the same machine in the end. a lisp compiler can just generate a C call instruction sequence.
19:36:33
nij-
Can I do this for other langs like python? Or does it just work for lower level langs like C?
19:39:18
aeth
the problem with other languages is that they have a much larger runtime, probably including a garbage collector, too
19:39:24
Bike
well, to run C code there probably does have to be some kind of c runtime linked in. like you're not going to be able to call into libc without linking libc.
19:40:06
Bike
C is fairly unique among programming languages in having defined binary interfaces for a variety of architectures, meaning that there is a clear and documented way to write the machine instructions to call into C
19:40:11
nij-
so for other langs, it's better to have two of them communicating via a socket or something?
19:41:08
aeth
Yes, to talk between language A and language B, you have them both pretend to talk to language C, in this case C.
19:43:04
Bike
nah, it's just that lisp and python aren't set up to talk directly, but they are both set up to talk to c. so to speak.
19:44:48
contrapunctus
nij-: pick up one of these forks - https://github.com/pinterface/burgled-batteries/network/members (unless there's something newer I don't know of...)
19:45:02
Bike
or have them in separate processes and talk over sockets. depends on what you are doing.
19:46:00
Bike
it has a runtime, but you only need to care about crt0 if you're doing pretty funky stuff
19:47:43
aeth
I was just thinking about the most overengineered to do it, which is actually probably also the most common
19:48:52
Bike
if stuff is in the same process, you might be able to send a pointer to it, so there wouldn't be any copying and it would go quick. over a socket you'd have to actually send the data. but there are other things you could do, like mmapping
19:49:35
aeth
if you don't care about the implementation and it's your highest priority, you could use a runtime that's designed for that
19:50:55
aeth
but the other language implementations might not be as fast or feature rich as their main implementations
19:51:16
nij-
Bike - about the same process could save time: different lang interprets data differently.
19:51:34
nij-
So even if they are in the same process, being able to point to the same address doesn't necessary means it's fastr right?
19:52:41
aeth
nij-: you'd probably try to meet in the middle. If something lower level than python, maybe it can have octet arrays
19:53:00
Bike
depends on the nature of the data. cffi lets you write to memory pretty much like you'd do in C, so C can read it without any kind of translation.
19:53:17
Bike
all of this stuff pretty much depends on what exactly it is you are doing. without knowing that i'm going to have to be pretty vague.
19:54:26
aeth
nij-: but there are a lot of different approaches, e.g. you could put it in a separate program, launch it with uiop:launch-program, and communicate that way (inter-process, I think).