I think we all know what you meant and that the way you said is a common way to say it, but being common doesn't make it correct. I wish people would rephrase that as "Lisp has a simple, fairly uniform syntax" or something like that. At any rate that's not the important point! What matters is the other thing you said: that programs in Lisp are represented in terms of the convenient, built-in data structures, the famous 'homoiconicity'.
Lisp has a two-level syntax. Remember: syntax decribes the structure of valid programs in a language.
First level is the syntax of s-expressions and its data types: numbers, symbols, strings, conses, lists, characters, vectors, ...
The second level is the syntax of the programming language Lisp:
* function calls
* built-in special operators: IF, LET, FUNCTION, PROGN, ... together with lambda expresssions
* macros: macros can provide arbitrarily complex syntax. Examples: DEFUN, DEFCLASS, DEFMETHOD, DEFMACRO, HANDLER-CASE, LOOP, DOTIMES, ...
Could we say that concetanative languages like forth have no syntax. It seems to me there is no AST in those languages, just a stack.
I'm not formally trained in CS, so that's my own individual opinnion.
1. Lisp is written preorder; Forth is written postorder.
2. The number of children a node in your Lisp AST has is marked explicitly, by parentheses, whereas the number of children a node in your Forth AST has is defined externally.
There is no difference between a Lisp-style
(+ (* 3 5 8) (+ 4 1))
and a Forth-style 3 5 8 * * 4 1 + +
except that the * in Forth-style can't take a variable number of arguments. But if you added parens to Forth, it could: ((3 5 8 *) (4 1 +) +) 3 5 8 3 N* 4 1 + +
The equivalent of (+ (* 3 5 8) 4 1) could be 3 5 8 3 N* 4 1 3 N+> the number of children a node in your Forth AST has is defined externally
and that's basically why.
Here's a 1000 node AST:
foo
/ \
Where are the rest of the nodes? Oh, they are defined externally.LET is an example. LET provides bindings for variables:
LET
a list of bindings
a sequence of declarations
a sequence of expressions
(LET ((a 1)
(b 2)
c)
(declare (integer a))
(declare (type (integer 0 100) b)
(setq c (+ a b))
(expt c 2))
The EBNF syntax for LET is: let ({var | (var [init-form])}*)
declaration*
form*
DECLARATION and FORM have more syntax.If Lisp sees a LET form which does not use above syntax, then it is a syntax error. A compiler or interpreter will tell you that. The compiler will detect that at compile time.
The following forms are all errors:
(let a 10 (+ a 10))
(let (a 10) (+ a 10)) ; 10 is not an identifier
(LET (((A) 10)) (+ a 10)) ; (a) is not an identifier
(let ((a 10)) (+ a 10) (declare (integer a))) ; declaration at wrong place
(let ((a = 10)) (a + 10)) ; Lisp has no infix operators...Hogwash.
What's the difference between a list and a cons pair? That simple "." makes a HUGE world of difference when trying to parse S-expressions. That's syntax.
Why is there a difference between macros and reader macros? That's also syntax.
About as far as you can go is that "In Lisp, code and data have the same syntax." And even that is a bit dicey.
Does that make more sense to you?
What is dicey about code and data having the same representation? You use the word 'syntax' rather than representation but s-expressions is not a syntax per se.
defun function-name argument-list declarations/documentation forms
Above is syntax.An example of a function using a lot of syntax:
(defun foo (n &optional (a 10 a-p))
"This is the function FOO"
(declare (type (integer 0 *) n a))
(declare (ignore a a-p))
(loop for i from 0 below n by 10
sum i into s1
sum (expt i 2) into s2
finally (return (/ s1 s2))))
Lisp has complex argument lists with required, keyword and optional arguments.It has syntax for a bunch of declarations.
It has macros like the LOOP macro which implement a lot of syntax. Actually the syntax of LOOP is quite long.
PS: I like all your coments on Lisp, Rainer. Very informative.
Conversational Lisp, 1973, in Interlisp
(FOR OLD X<-M TO N DO (PRINT X) WHILE (PRIMEP X))
Common Lisp (LOOP FOR X FROM M TO N DO (PRINT X) WHILE (PRIMEP X))
Warren's greater idea was to have a very forgiving syntax for Lisp, with spell checker and DWIM (do what I mean) support. Write expressions in different order, with spelling errors, etc. Lisp will figure out what you mean.The function UNION in CLISP (see https://www.dreamsongs.com/Files/Hopl2.pdf ):
DEFINEQ((UNION (LAMBDA (X Y)
(IF ~X THEN Y
ELSEIF X:1 MEMBER Y THEN UNION X::1 Y
ELSE <X:1 !(UNION X::1 Y)>]
In MLISP: EXPR UNION (X,Y)
IF ¬X THEN Y ELSE
IF X[1] ε Y THEN UNION(X↓1,Y)
ELSE X[1] CONS UNION(X↓1,Y);
In CGOL (Algol syntax on top of Lisp) define x "UNION" y, 14, 13;
if not x then y
else if member(car x, y) then cdr x union y
else car x . cdr x union y <>
or CGOL with extended character sets: define x "∪" y, 14, 13;
if ¬x then y else if αx ε y then βx ∪ y else αx . βx ∪ y <>
'Conversational' syntax elements were for example often used in some AI systems as user interfaces into knowledge based systems. Rules were often written in similar expressions, example from KEE: (IF (THE SWITCH OF LIGHT IS ON)
(THE LOOK OF LAMP IS NO.LIGHT)
THEN (DELETE (THE STATUS OF LAMP IS WORKING))
(THE STATUS OF ROOM IS DARK))
Though I'd think the main purpose of LOOP is to have an example that Lisp can incorporate a wider range of embedded syntax variants and to make the language purists run away...Having exactly the same languages makes macro writing really easy.
Lisp doesn't have more parentheses than other languages:
(if (validate foo)
(println foo "is the right answer"))
vs if (foo.validate()) {
console.log(foo, "is the right answer");
}
Unless you consider curly braces special and virtuous, and "(" parens bad. if foo.validate() {
println!("{} is the right answer", foo);
}
This has less parentheses (unless you count the placeholder in the format string). Putting the condition in parentheses like C and friends do is redundant if you use braces for the block anyway. (procedure foo ((integer x) (in-out integer y))
(set y (+ 3 x y))))
;; https://sites.google.com/site/sabraonthehill/postmodern-examples/postmodern-intro-to-s-sql
(query (:order-by
(:select 'countries.name
:from 'countries
:where (:= 'name '$1)))
"Vietnam")Edit? Oh hell yes. Once someone groks paredit (a weekend to get fluent), every other language is painful. It's because you're editing in terms of complete forms, rather than in units mismatched to the actual code.
Remember your first text editing experience? You probably spent a long time editing in terms of characters. Then remember what a revelation it was when you learned a programmers editor and started being able to manipulate larger portions of text such as words, lines, and even paragraphs?
Paredit is like that, but the next level, because you're working at the level of arbitrary syntactic forms.