reddit / emacs
Emacs lisp question concerning code as data (I think? please don't yell at me)
[deleted] · 2022-12-07 03:05
[deleted]
agrostis · 2022-12-07 03:30
I don't quite get your intent. What would you like the output to be? With or without double quotes around member strings? With or without separator spaces between them? With or without parentheses around the whole?
lenins_cats · 2022-12-07 06:38
Apologies, with double quotes, with space separator, without parentheses around the whole
agrostis · 2022-12-07 10:12
Ok, I think I see your problem. The thing is, you need to distinguish between data as it exists inside Lisp, on one side, and its external representation, on the other. When you see something like
("foo" "bar")
it can be understood on two levels. Outwardly, it's a string of thirteen characters: ``(``, ``"``, ``f``, ``o``, ``o``, ``"``, ``␣``, ``"``, ``b``, ``a``, ``r``, ``"``, ``)``. The parentheses, the quote signs and the space are all part of it. There's no internal structure, it's just a sequence of characters. However, it can be fed to the Lisp parser, which sees it as complex data literal — a serialization format, if you will — and parses it into a data structure, namely, a list whose elements are the three-character strings ``foo`` and ``bar``. There are no more parentheses or double-quote delimiters on this level, just the internal data, made up of cons cells and character arrays. It can be represented diagrammatically in this way:
cons cons
┌───┬───┐ ┌───┬───┐
│ • │ • → │ • │ × │
└─↓─┴───┘ └─↓─┴───┘
string string
┌─┬─┬─┐ ┌─┬─┬─┐
│f│o│o│ │b│a│r│
└─┴─┴─┘ └─┴─┴─┘
A data structure like this is an *object* (or *value*), as it is called in Lisp docs. It can be passed in and out of functions and stored in cons cells and vectors. Technically, what is thus manipulated is a reference to the first cons cell, but you can usually abstract away from these details. Conversely, when you evaluate Lisp code — e. g., in the ``*scratch*`` buffer — the result is printed as the external representation.
An external representation is not a Lisp object per se. It only becomes an object when it is encoded as a string. So, when we consider ``("foo" "bar")`` as plain character data, rather as a Lisp literal, we can illustrate it with the diagram
string
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│(│"│f│o│o│"│␣│"│b│a│r│"│)│
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
A string which is an external representation of Lisp data can be passed to the function ``read-from-string``, which reifies the Lisp data parser. This function returns a cons cell with two values: the parsed object, and the position of the last read character:
(read-from-string "100")
⇒ (100 . 3) ;; 100 represents a number
(read-from-string "foo")
⇒ (foo . 3) ;; foo represents a symbol
(read-from-string "\"foo\"")
⇒ ("foo" . 5) ;; "foo" represents a string
(read-from-string "(foo 5 \"bar\")")
⇒ ((foo 5 "bar") . 13) ;; a 3-element list
Of course, not every string represents Lisp data. If we try to parse a string which is not an external representation of a datum, the parser either throws an error or scans only a portion of the string (if an initial portion of it does represent a datum:
(read-from-string "(foo")
ERROR: End of file during parsing
(read-from-string ". a")
ERROR: Invalid read syntax: "."
(read-from-string "foo bar")
⇒ (foo . 3) ;; the " bar" part is ignored
So, coming back to your question. The thing you want —
"example" "list"
is not a representation of any value. You can regard it as character data (not Lisp code) and manipulate it as a 16-character string. Or else you can regard it as *two* external representations, string literals, one of a 7-character string, another of a 4-character string. You can manipulate them individually, e. g. put them in a list. You can also write a parser for a language / data format in which such notations actually represent data structures. It won't be Lisp, though it can use Lisp functionality internally.
deaddyfreddy · 2022-12-07 11:50
could you provide input/output examples as lisp code?
agrostis · 2022-12-07 13:32
As regards “code as data”, that's one step above parsing. The idea is as follows. A Lisp data structure can be interpreted as a program which evaluates to an object and possibly produces some side effects (i/o and changes of state). Strongly simplifying:
* Any object which is neither a symbol nor a list is interpreted as a self-evaluating program (i. e., which evaluates to itself).
* A symbol is interpreted as the name of a variable, except when it's the first element of a list or a keyword in a syntactic construct.
* A list whose first element is a symbol which is not from the set of special symbols, is interpreted as a function call. E. g., ``(cons 12 5)`` represents a list whose first element is the symbol ``cons``. It is interpreted as the application of the function named ``cons`` to the numbers 12 and 5.
* A list whose first element is one of the special symbols is a syntactic construct. Every syntactic construct is interpreted according to peculiar rules. E. g., ``(defvar foo 50)`` parses to a list whose first elements is the symbol ``defvar``. It is interpreted as a variable definition construct.
* Any other kind of list is not a valid program.
That is, Lisp data can form a very transparent AST. Most importantly, it need not come directly from the parser, but can be constructed on the fly from symbols and cons cells.
andyjda · 2022-12-07 14:17
TLDR: This should work:
(mapconcat 'prin1-to-string (list "example" "list") " ")
My advice is when you're working with a function you're not very familiar with, always start with `M-x describe-function`.
The output of `M-x describe-function mapconcat` is:
>mapconcat is a built-in function in ‘C source code’.(mapconcat FUNCTION SEQUENCE &optional SEPARATOR)Apply FUNCTION to each element of SEQUENCE, and concat the results as strings.In between each pair of results, stick in SEPARATOR. Thus, " " as SEPARATOR results in spaces between the values returned by FUNCTION.SEQUENCE may be a list, a vector, a bool-vector, or a string.Optional argument SEPARATOR must be a string, a vector, or a list ofcharacters; nil stands for the empty string.FUNCTION must be a function of one argument, and must return a value that is a sequence of characters: either a string, or a vector or list of numbers that are valid character codepoints.
Notice this part:
>Thus, " " as SEPARATOR results in spaces between the values returned by FUNCTION.
I tried this out:
(mapconcat 'prin1-to-string (list "example" "list") " ")
And it looks like the output is what you want: `"\"example\" \"list\""`. Note that the only way for double-quotes to be present within a string is for them to be preceded by '\\'. This basically tells your program to interpret the following quote as a character within the string, instead of the quote terminating the string.
When people talk about "code as data" in Lisps, they usually mean [syntactic macros](https://en.wikipedia.org/wiki/Macro_(computer_science)#Syntactic_macros), which you can look into and ask about if you're interested. But I don't think it applies to this question: you're just processing data, no need to use macros.
lenins_cats · 2022-12-08 04:04
Hey thanks for the writeup, much appreciated. I should have been a little more specific, I'm trying to make a call to call-process using mapconcat to split up a list of args as described in the edit
lenins_cats · 2022-12-08 04:04
>Hey thanks for the writeup, much appreciated. I should have been a little more specific, I'm trying to make a call to call-process using mapconcat to split up a list of args as described in the edit
andyjda · 2022-12-08 04:43
I see.
What part of it is not working as expected? It might be easier to help if you show the actual function you're writing and explain what you expect it to do.
Are you sure you want to concatenate the strings? Because I'm looking at `start-process` and the docs say:
>Remaining arguments PROGRAM-ARGS are strings to give program as arguments.
So `PROGRAM-ARGS` is a list of strings, not a single string of space-separated words. That means that if you want the arguments of your subprocess to be "foo" and "bar", you call `start-process` like this:
```
(start-process NAME BUFFER PROGRAM "foo" "bar")
```
Of course, it might be that for the purpose of your subprocess, a single `PROGRAM-ARG` in this format is what's needed. But in that case the code snippet I posted should help, and the call becomes:
```
(start-process NAME BUFFER PROGRAM
(mapconcat 'prin1-to-string (list "foo" "bar") " "))
```
lenins_cats · 2022-12-08 04:57
Thanks again. I’m specifically trying to make a multi arg cli call and am finding that the mapconcats escape characters and quotes screw up the cli formatting. I’m sure there’s a more sensible way to approach it but I wasn’t sure. Much appreciate your replies
andyjda · 2022-12-08 05:39
No problem :).
So just to be sure, calling the function with multiple strings, with no formatting, works, right?
​
(start-process NAME BUFFER PROGRAM "foo" "bar")
lenins_cats · 2022-12-08 06:24
Yeah it does, I’d just like to easily be able to pass multiple args in a list. If you’d suggest a better approach I have no attachment to the list angle
agrostis · 2022-12-08 07:01
What you need is [apply](https://www.gnu.org/software/emacs/manual/html_node/elisp/Calling-Functions.html#index-apply):
(let ((my-args (list "foo" "bar")) …)
(apply 'start-process my-name my-buffer my-program my-args))
lenins_cats · 2022-12-08 07:35
Perfect, I’ll give that a go!
lenins_cats · 2022-12-08 07:44
Is exactly what I needed thank you, hadn’t ever fully understood the use case for apply. Sorry for being so unclear, I wasn’t totally sure myself what I wanted to do, I had been using start process but had issues with it not being async