More integration, and it's more powerful. With python, you less so use REPL for development, and more for testing, while in Common Lisp, you develop with it.
You have REPL running, you can evaluate functions, and definitions on the fly, or completely re-define the function, even if said function is being called in a loop by other one, and it will work. Or, redefine classes, and it will be fine.
Or, if function errors, debugger gets launched, and you have option to redefine variables, and the function will continue with that variable (if, you called undefined variable)
Though, I believe, its better to see how it works, rather than read,
While the video is not mine,
https://www.youtube.com/watch?v=6pMyhrDcMzw
And remember, this is not hot reloading.
It is actually not about a "REPL" but "image-based" development. It is WAY ahead of the Python REPL, which was already a relief for me. The Python REPL is archaic in comparison.
The REPLs that come with Lisp, Smalltalk & Factor are in a different league.
There's probably a few different reasons to why but one thing that the 3 i've listed above all have in common is that they have image-based environments. So thats probably one of the top reasons why they have superior REPLs.
Lisp was my favorite prototype language… I could write programs 10x faster in Lisp. Lisp also made me a better programmer and taught me the superiority of functional programming.
I just started reading SICP yesterday after being convinced by PG's essays [0] and I would suggest just looking at the solutions, since the first chapter relies heavily on number theory concepts, and the authors say they take that choice because it makes introduction to more abstract ideas easier (as a math grad student I'm already on page 110). I personally think that to someone without a background in higher arithmetic it would be a bit boring, so I repeat that it wouldn't hurt to look at some solutions to get a feel to it [1]. The book is MIND-BLOWING and after years of C++ and Python I'm really excited of what I will find in the next chapers.
It's currently tied for 4th for the highest upvote rate, of the 30 posts on the front page. This isn't pg secretly manipulating HN to boost Common Lisp blogs; we're just organically crazy.
(I'm also confused who's enthusiastically upvoting 3-paragraph long text blogs).
52.7 points/minute :: "Sauna use as a lifestyle practice to extend healthspan (sciencedirect.com)"
38.0 points/minute :: "Japan WWII poison gas agents still scarring people today (mainichi.jp)"
36.8 points/minute :: "Baker's Math (thefreshloaf.com)"
36.0 points/minute :: "Cobra Maneuver (wikipedia.org)"
36.0 points/minute :: "I Just Can't Kick Lisp (djha.skin)"
33.0 points/minute :: "Ubisoft+Bungie Scrambling to Evacuate Players Games from Capsizing Google Stadia (forbes.com/sites/paultassi)"
&c...
Interesting claim implied here, and maybe some truth in it, that there
are highbrow code cultures that attract 'better' types of hacker to
the club.
Personally I've never felt ashamed to roll up my sleeves and get some
oil in my beard with C++. Perl, Python or really filthy Bash
scripting.
But having learned Lisp (SICP) back in the 80s, It does sometimes feel
like I took Latin and Greek at prep-school, and ever since have been
slumming it with my working class coder pals.
Total geek snobbery for sure. But is there anything in it? Please say
no.
I've known engineers who would swear up, down, and center that using Haskell or Lisp or Clojure made them more productive. More features, better, and faster.
From the outside, I didn't see any evidence of this. The people who were better and more experienced engineers tended to produce more, better, and faster. I saw that they were happier using the tools of choice. As far as I could tell the supposed extra productivity over Rails or some other modern framework for the task at hand was largely illusory.
On the one hand, happier engineers is good. On the other hand, code in relatively obscure languages is more difficult to find people to maintain and libraries are less likely to be kept patched by the community. It's a tradeoff. I wouldn't say one is better.
The primary gain from Lisp and similar is, in my experience, not that it's useful for writing software. It's that learning a different approach teaches you new ways to attack problems. Sometimes those are useful.
> From the outside, I didn't see any evidence of this. The people who were better and more experienced engineers tended to produce more, better, and faster. I saw that they were happier using the tools of choice. As far as I could tell the supposed extra productivity over Rails or some other modern framework for the task at hand was largely illusory.
I don’t think the claim is that one has to use those older technologies exclusively. I read the GP’s comment as that the knowledge one gains by learning those older technologies gives one more tools in their bag when using modern technology.
> The primary gain from Lisp and similar is, in my experience, not that it's useful for writing software. It's that learning a different approach teaches you new ways to attack problems. Sometimes those are useful.
Exactly. An example: trying to learn functional programming? Erlang will force one into that mindset. It is natural to spot mapping, folding, flattening in any other language later on. Working with local data, or state machines becomes a habit because there’s no other way of doing things in Erlang. That knowledge is directly applicable in any other language.
> The primary gain from Lisp and similar is, in my experience, not that it's useful for writing software. It's that learning a different approach teaches you new ways to attack problems. Sometimes those are useful.
This exactly describes my experience with Clojure. Sure, I learned “functional programming” which was a “different paradigm”, but at the end of the day what I learned was how to reason about:
1. Whether some bit of state is intrinsic, not strictly necessary but it is highly convenient, or neither of those. (The highly convenient case in Clojure is mostly a reference to Transients[1], which I don’t think I ever used. But the concept has been incredibly valuable when transferring what I’d learned to more imperative languages.)
2. There are correct and obvious function boundaries even for quite a lot of domain specific code, and they usually become evident while you work if the previous point is applied judiciously.
3. Some general purpose abstractions are so self-evidently useful that adopting them is never premature. (Here I’m thinking of what Clojure abstracts as `seq` and the enormous set of functions which operate on `seq`-conforming collection types. Other FP languages similarly bring their own wealth of broadly applicable abstractions.)
4. Many, maybe most, functions have a correct parameter order, which is the order you’d use for true lambdas. Once learned, it can almost always be applied subconsciously.
5. Metaprogramming is good, actually, if you have a good reason to do it. And having a good foundation for metaprogramming is invaluable even if its value is hard to elucidate. (Macros, of course. But I’d give my figurative left kidney to have time to explore the kinds of problems I could potentially solve with Racket’s language-oriented features.)
For another day or at least a later comment, 6. Lack of static types in most lisps hilariously undermines each one of the points above.
I wouldn’t call it snobbery. I consider Lisp, Prolog, and Erlang to be the three most profound programming languages of all time. Maybe they lack some utility for certain problem domains, maybe they don’t have vast ecosystems today, but all three teach how to arrive at solutions in the most logical, pure, functional way, without using global state.
This knowledge is then applicable to any other programming language.
I wanted to love Lisp after learning about it in "Gödel, Escher, Bach", and then in PG's Blub essay (which is why I'm a HN fan).
The syntax is just off-putting and I was never compelled to learn it in school, so I remain in algol-land. If time and focus allowed I'd do the SICP self study path but it's too easy to surf HN and Reddit instead.
SICP is a good way to learn about programming from first principles. It is not the best way to learn CL. CL and scheme are extremely different despite having similar roots.
It's not even the best book to learn actual Scheme. SICP's main purpose is to teach computer science basics using a tiny subset of Scheme (no macros, no error handling, no libraries, ...).
That says (some) implementations had macros from the beginning, though those macros were much different than what was eventually standardized in (I think) r5rs. I guess I can believe that. I don't know when Guile got macros.
SICP was largely written before Scheme was standardized and before SRFI existed.
For example SICP uses a so-called special form called cons-stream, which is in no Scheme definition (it was in no Scheme reports) and for which no implementation is given in the book, since it can't be implemented by the Scheme subset used for the book. It assumes that it is somehow provided without giving any details.
Thanks, yeah ok, it hadn't occurred to me that cons-stream has to be a macro. I also remember the book also explaining how force and delay work in terms of wrapping and later evaluating a lambda, but don't remember if it explained that delay has to be a macro too. I guess the Common Lisp approach to macro hygiene is using gensyms for temporaries, but Schemers saw that as too ugly. I wonder how Clojure does it. I'm not that big on Scheme or Lisp these days anyway though.
No personal attack intended, but what I always find funny: the complaints about Lisp's syntax. Lisp's syntax is regular prefix notation all over and one of the defining features of the language.
Algol languages on the other hand, and math even on paper, are a rather wild bunch: they mix infix operators like plus/minus (1+1), multiplication/division (2*2), have postfix operators like faculty (9!) and even user defined functions f(x, y) in prefix notation. That should be way more off-putting theoretically, but people just grew up with this "mess" and think that fine.
Disclaimer: besides Lisp, I program in Perl or Shell too ;-)
Macros don't add syntax; they just add semantics to syntax that already exists.
You can write down the syntax of a call to a macro which has not yet been written, and Lisp will read it for you, giving you the tree. It just won't be able to expand it since the semantic definition through the macro name isn't there.
A conventional language will not do that. C++11 will not correctly read the shape of a C++17 phrase structure, and just complain about it not being defined.
Lisp macro can treat their arguments as a flat stream of tokens and parse them, even with LALR(1) parser generator or whatever. It's not not the typical way most macros are written. Macros leverage the ability of the invocation syntax to express pre-parsed nested structures.
Even if a macro goes crazy like that, it's still self-contained; the churning toilet bowl of parsing it has whipped up is cleanly confined within the boundaries of the macro call, which can be cleanly substituted anywhere an expression can be used.
If you don't know the syntax of let, then you might find yourself writing
(let (x 5) (* x x))
where you should have written
(let ((x 5)) (* x x))
That is syntax.
Let me ask you a question: Are these two expressions equivalent?
(f (g x))
and
(let ((y (g x))) (f y))
In the absence of macros, the answer is yes. In the presence of macros, there's no way of knowing until you've seen the definition of f: If f is a macro, they might not be. You need to know the syntax of the f macro to answer the question.
CL has a core language with a simple syntax. It also has a lot of macros in its standard library.
Even though (let (x 5) (* x x)) is wrong, it parses. We know that it has an operator, followed by two arguments, and the first argument is made up of two things.
Because it parses, I don't have to correct the author's punctuation, just their understanding of the necessary shape . The wrong shape they wrote has a coherent syntax, and we can discuss the problem in terms of differences in syntax, rather than differences between gibberish and valid syntax.
This is why your question is able to be, "are these expressions equivalent" and not "are these two token sequences equivalent".
I can write a TDD test case for a new macro that doesn't exist yet, which won't bring down the program.
E.g. (test (my:let (x 5) (+ x x)) 10)
This will catch the error that foo is not defined and report that the test case failed instead of returning the expected 42 value. The next test case in the same file can execute.
You couldn't have a test for new C++ or Java syntax, in a C++ or Java file being processed by a compiler that wasn't maintained in order to recognize the syntax. The file will blow up at parse time, not allowing any of the test cases in it to proceed.
> You need to know the syntax of the f macro to answer the question.
But I can see what that syntax is: it's (f (g x)) right there: i.e.
/\
f /\
/\ ()
g /\
x ()
g and x are clumped together, so they have a closer relationship of some kind than either one has with f, at least syntactically. From looking at the use, I don't know whether it is valid shape for f, or if so, what other shapes are valid. And I don't know the semantics at all.
I'm not struggling with syntax, though, just connecting it with a meaning.
> Even though (let (x 5) (* x x)) is wrong, it parses.
That's not the definition of syntax.
If you're implementing a compiler, then you might use "syntax" as a shorthand for those parts of the syntax that are processed in a particular "syntax checking" stage of the compiler. But that's not all it is.
> From looking at the use, I don't know whether it is valid shape for f
That, on the other hand, is close to a definition of syntax: The rules for what has a valid shape and what doesn't.
In fact, in some cases there is nothing to do, syntactically, after the reader is done. An expression like (+ (* x y) (sqrt z)), after being read, is ready for evaluation by performing the function calls. (In principle; of course since we have macros, it has to be walked.)
It was the reader which settled all the syntactic facts about what operation is applied to what operands.
At this point we could move the goalposts: even eval and compile don't know anything about Lisp syntax. Because, look: (- 10) and (- 10 1) behave differently: the 10 operand changes semantic role between minuend and subtrahend based on syntax. All that eval knows is that it's passing arguments.
A function can treat its argument values as the tokens of a syntax which are shored up according to arbitrary phrase structure rules, similarly to Unix programs like find and tcpdump.
> It was the reader which settled all the syntactic facts about what operation is applied to what operands.
The reader does not know anything about operands or operators. It does not even know if the thing read is data or code. It also does not know if it is valid code. Enter (3 4 *) and the reader happily takes that expression, not caring about operators and operands. The reader will print zero warning about such expressions.
To really know what the symbols in an expression actually are, one would need eval, compile or a code walker. Are they used as literal symbols? variables? functions? macros? block names? type names? ...
> Because, look: (- 10) and (- 10 1) behave differently: the 10 operand changes semantic role between minuend and subtrahend based on syntax. All that eval knows is that it's passing arguments.
That's semantics, not syntax. EVAL and COMPILE check that it is a function call, what arglist it expects (zero or more args, &optional args, &rest args, keyword args, and process them recursively. But Lisp has also special operators, macro operators and a lambda expression. There the syntax requirements are different or even context depended.
Also if Lisp reads the expression (foo * +), the reader does not know if FOO is a function or a macro, it thus does not know what * and + are. Are they variables? Are they operators? Something else? They don't even need to have a definition at read time.
The parser for a C grammar (e.g. Yacc job) also doesn't know anything. Is the output used for pretty-printing (the code is just data), completion, or generating code?
The reader doesn't know about opperands or operators, but it certainly knows a symbol from an integer or string.
We've cleverly arranged things so that it doesn't have to know anything else in order to produce output that, in some cases, can be evaluated directly.
> That's semantics, not syntax
Syntax versus semantics is sometimes about where you put the goalposts.
What if I make it a macro that looks at the argument count and chooses a different function?
(defmacro minus (&rest args)
(if (eql 1 (length args))
`(unary-minus ,(car args)) ;; takes exactly one arg
`(nary-minus ,@args))) ;; blows up if not given at least two args
Is that still "semantics, not syntax"? Why does it become semantics if one function does it itself by switching on the argument count internally?
Everything is syntax! Adding two floating-point value is syntax: the representation has to be parsed into sign bit, exponent and mantissa, and so on before anything happens.
Sure, those fields are accessed conveniently by position, but so are the elements of the list (sign exponent mantissa), and our goalposts say that that is syntax.
The parsing never stops. The CPU fetches instructions, decodes (== parsing!) and executes.
When the Lisp run-time looks at a value to extract the tag bits, and then further delves into the object accordingly, it is parsing syntax.
> The reader doesn't know about opperands or operators, but it certainly knows a symbol from an integer or string.
That's the s-expression syntax. Beyond that, symbols have no special meaning for the reader.
> Everything is syntax
Syntax describes the structure of a language.
Wikipedia: "the syntax of a computer language is the rules that defines the combinations of symbols that are considered to be correctly structured statements or expressions in that language."
CLHS 1.4.1.2 "This specification uses an extended Backus Normal Form (BNF) to describe the syntax of Common Lisp macro forms and special forms."
So the ANSI CL standard claims that its macro forms and special forms have syntax and that this syntax is described by an eBNF?
* (do "hello")
debugger invoked on a SB-KERNEL::ARG-COUNT-ERROR in thread
#<THREAD "main thread" RUNNING {1004AC01C3}>:
Error while parsing arguments to DEFMACRO DO:
too few elements in
("hello")
to satisfy lambda list
(VARLIST ENDLIST &BODY BODY):
at least 2 expected, but got 1
It tells me that there is an error while parsing (!) arguments to the DEFMACRO DO.
Woops? There is a parser? There is an error detected?
So, beyond the the reader SBCL seems to claim it has a parser?
You used the argument that the Lisp reader isn't parsing Lisp because it doesn't actually know Lisp; it has no idea what the operators mean.
But, above, the message "error while parsing arguments to DEFMACRO DO" is just a destructuring error on "macro lambda list" pattern match on a piece of datum: the unevaluated macro call. That pattern matcher has no idea what DO means, in much same way that the reader doesn't know what DO means.
The reader has done so much work, that the remaining parsing is just a very easy job which enjoys easy positional access to everything in the tree, at every level, at worst having to loop over a sequence of similar items.
is relying on read syntax; it has parentheses in it! Moreover, it is a greatly condensed specification, due to its reliance on the properties of the read syntax.
The init-form can be anything: a struct literal, vector, string, character. Yet, I don't see the rules for that in this grammar; they given elsewhere, and just assumed here.
If everything were to be made explicit here, the way a handful of parentheses have been made explicit, it would be pretty large specification, of which the do-specific parts above would be small.
The do-specific material is just a handful of self-contained patterns applied to individual objects that have already been parsed out by the reader.
For instance, the pattern pattern {var | (var [init-form [step-form]])}* is understood to be applied to that part of the form which can be conveniently retrieved as (cadr form). The amount of work left is very small, of low complexity. The pattern is not a recursive language; it is a regular language (if we pretend that var, init-form and step-form are terminal symbols).
When the (var init-form step-form) is matched against the corresponding object, the step-form is easily retrieved by position, regardless of the complexity of init-form. This is not true of the unparsed expression in which scanning across an init-form to get to step-form is a complex step. The reader has done the work which allows this stage of processing to treat "init-form" as if it were a terminal symbol in a simple regular grammar that is just concerned with a positionally-determined portion of the do syntax.
It looks like Lisp has a lot of syntax beyond the read syntax if we look at all of the forms together. They all share the read syntax, but have their unique bits that drive their shape. The more of them there are, the larger is the proportion of the language syntax that is contributed by those unique bits. Each time we write a macro and document it, the read syntax doesn't grow, but the amount of deep syntax does.
From the perspective of any one S-expression, though, the bulk of the specification of its syntax is in the domain of the reader. The pattern match for a specific shape like do is a small amount of easily digestible additional information.
> That pattern matcher has no idea what DO means, in much same way that the reader doesn't know what DO means.
'means' is in the domain of semantics. We talk about syntax: structure.
But what we know at this stage: DO is the CL:DO macro. So we know the actual operator, not just the symbol. We also know that it is a macro. We also know that the macro has a macro lambda list (CLHS 3.4.4 describes the syntax of those). We can now check the macro form against the actual CL:DO macro lambda list structure. And so on...
Thus we are now talking about the Lisp language, its operators and its structure requirements.
That's the purpose of a language parser: it has to know which symbols/identifiers are actual elements of the language, which operators these actually are and retrieves the syntactic constraints/rules for those.
In a language like Common Lisp (or similar dialects) this is 'difficult', since there are lexical bindings, compile-time side-effects and even lexical bindings to establish new syntax (macrolet).
> The syntax notation like: ... is relying on read syntax; it has parentheses in it!
It describes constraints on the structure (!) of valid Lisp forms. -> syntax!
(do () () "foo") -> Ill-formed DO end test list
(do () (t) "foo") -> no legal go tag
(do ((a b c d)) (t)) -> illegal form for a DO varlist
See that the warnings talk about the actual Lisp operators and their requirements? We've left the domain of s-expression syntax here and talk about Lisp syntax requirements.
None of these things are caught by the READER. All of these are actual warnings from SBCL.
> The init-form can be anything: a struct literal, vector, string, character. Yet, I don't see the rules for that in this grammar; they given elsewhere, and just assumed here.
The rules are in the language standard. The non-terminal INIT-FORM is specified to be a FORM. Which then is defined elsewhere.
> The init-form can be anything
Actually it can't. It must be a form (see the CLHS syntax description for DO). Thus ((foo)) for example is not valid, since it is not a valid Common Lisp form.
> From the perspective of any one S-expression, though, the bulk of the specification of its syntax is in the domain of the reader.
This is tautologic.
I was claiming that Lisp has structural constraints/rules on top (!) of s-expressions (and especially s-expressions as data) and gave the example of the Common Lisp standard, which describes these in an eBNF syntax.
Lisp does have "syntax" but it doesn't have syntactic sugar. But yes, it's very minimalistic syntactically and the "sugar" might be what people are missing on first contact. And the dreaded parenthesis? That's a thing my editor took care of in the last 30+ years.
Every Lisp has syntax. But the code is actual data and there is a separate syntax for this data, called s-expressions.
Source code is actually simple nested list data. For example the following snippet, which binds two variables A and B, defines a local function and then calls this local function. This is just an s-expression, which consists of symbols, numbers and lists.
Even though s-expressions have a syntax with numbers, parentheses, symbols, ... - they can not only be written in a text editor, one can also directly compute with them using the typical Lisp operators for lists, numbers, strings, symbols, ...
The following enters literal data, which is not executed as code.
CL-USER 5 > '(let ((a (+ 1 2))
(b (* 2 3)))
(flet ((add3 (a b)
(+ 3 a b)))
(add3 a b)))
(LET ((A (+ 1 2))
(B (* 2 3)))
(FLET ((ADD3 (A B)
(+ 3 A B)))
(ADD3 A B)))
We can substitute the symbol * with the symbol expt in this s-expression - the variable * just holds the last evaluation result:
CL-USER 6 > (subst 'expt '* *)
(LET ((A (+ 1 2))
(B (EXPT 2 3)))
(FLET ((ADD3 (A B)
(+ 3 A B)))
(ADD3 A B)))
Then this can be evaluated:
CL-USER 7 > (eval *)
14
Now, even though the program is written as a data structure, the Lisp program itself has syntax. Only function calls have the simple (operator args...) syntax. But there are also special forms and macro forms.
LET is a syntactic form and expects a list of (variable value) pairs as its first sub-expression. Then follows a body of subexpressions, where the variables can be used.
FLET is a another syntactic form: it expects a list of function definitions and a body. The function definition starts with a name, followed by an arglist and then by a body.
In reality this is more complex, since LET and FLET allow declarations, functions allow complex argument lists, declarations, documentations, etc.
So for FLET the top-level EBNF syntax would be like:
No offense taken. My comment wasn't meant to complain about it, just that it never clicked for me. I admit that I haven't tried hard or long enough but I've made peace with the fact that I have only so many hours/brain cells/etc and it's ok to not master all the things I think I should.
(Yes, this is an exaggerated example but this really is the point. Also, static types. If there's anything of overarching regularity in what you type, it better be syntax or it will be hard to check and take advantage of. This is what Algol-style languages do for systems programming.)
I program in Common Lisp professionally and this is baloney. People do use common patterns, offered by the language and open-source libraries.
What is being discussed in this sub-thread is an oft-cited criticism of Lisp's "power" being too much for its own good, but rarely (if ever) a reflection of idiomatic practice.
Ok, you can use libraries, of course - so shouldn't say that there is no reuse. But libraries are not the same as built-in syntax. Built-in syntax for the basic things, as in the Algol-family is a) standard so everybody uses it. b) optimized for the right mix of efficiency as well as redundancy (error detection, skimmability by humans). Talk about "oatmeal with fingerclippings mixed in" - more parens don't add useful redundancy, but keywords and delimiter tokens do.
Too much power for its own good is one thing, but being just not optimized for the common thing is another. How would the following function, randomly picked from what I have open on github right now, look in "Common" Lisp? And if I ask 3 persons to do it, what is the likelyhood that they would pick more-or-less the same approach?
CL has built in syntax for function definitions, local variable definitions, type declarations, function calls, loops, assignments, increments, value returning and a lot more.
It just uses names operators in prefix position. foo += 1 then is (incf foo).
for (...) { ... } is then (loop for ... do ...).
...i32 counter = 0;... is then (let ((counter 0)) (declare (type i32 counter)) ...)
As the sibling commenter said, that code admits a relatively standard translation. Without understanding the nuance of your code, and assuming you've made your own data structures and didn't use Lisp's standard ones (e.g., Lisp's native lists for the `batch` object), the translation would be this:
Could you adorn this code with type declarations? Yes, and you'd get speed and/or safety benefits by doing so. Depends how much this function "matters" in the larger context.
One difference that stands out here is that in the original code, the methods "node->next" and "node->edit" both belong to a "node" namespace, so you could have other objects with the same method name, e.g. perhaps "document->edit(patch)", and there would be no collision since they're in different namespaces.
But in your lisp version "next" and "edit" appear to be global methods. That seems like it could lead to a lot of naming collisions. Could you still have a document type that you can call "(edit document patch)" on? A preliminary Google search suggests that CLOS methods can't have definitions with a different number of parameters, so now you're stuck with a global "edit" method that can only ever take one parameter.
Common Lisp has generic functions, as you saw. Readers, writers, and/or accessors to classes are generic. So no issue with naming collisions.
(defclass a ()
((value :reader value)
(next :reader next)))
(defclass b ()
((value :reader value)
(next :reader next)))
This is totally valid; I can call VALUE and NEXT on instances of both A and B just fine.
But, if generic functions aren't your thing, you can skip calling the functions even and just do
(slot-value x 'next)
if you really want. This is basically
x->next
Again, works for any object with a field named by the symbol "next". But it's idiomatic to just call the reader function you defined, since getters can be instrumented, debugged, traced, etc. Some people prefer to call the reader A-NEXT or B-NEXT, but it's not necessary.
Regardless of all of that, symbols themselves belong to a namespace, called the "package". So your "edit" and my "edit" methods can have a different number of arguments without issue. (If they do have different arguments, they must mean different things.)
In Lisp this is solved with some combination of better names and symbol packages.
E.g. you might have some doc-model:edit symbol and file-utils:edit or whatever. The symbols have the same name, but are different objects in different packages.
The generic function doc-model:edit is therefore unrelated to file:edit.
Packaging at the symbol level means that the package system is relevant to anything whatsoever that is named by a symbol. If you have some logic programming system where you declare some named rules or facts, they can easily go into a package. Symbols which are quoted individually or in a larger literal are in the package system all the same; it's an independent layer.
Firstly, that needs a rewrite in some non-sucky curly brace with pointers:
Edit_Array buffer_batch_array_from_linked_list(Arena *arena, Batch_Edit *batch, i32 count)
{
Edit_Array result(arena, count); // Edit_Array has this thingy called a constructor
foreach (node in batch) // Batch_Edit * is somewhere/somehow declared iterable
{
if (result.full()) // Edit_Array has internal fill index, checked against count
break;
result.add(batch->edit); // Edit_Array has method to add and increment fill index.
}
return result; // not a function, drop the parens
}
The Edit_Array class in the original is of no help whatsoever. Like this coder or language aren't even up to abstract data types with Modula-2.
I've been thinking about this thread a lot, because I did not agree with you at all based on my experience with CL.
After a while I realized it - I may have programmed in Common Lisp but never professionally... on a professional code base. Only ever on open source thingies, and small side projects. Paid or unpaid.
I've never seen industrial common lisp code. My last job had some but I never got to work on that project, and no longer can see it :(
Is there an example somewhere of this? C++ examples would be things like Chrome, C would be lots of open source OS's and kernel drivers. Is there something equivalent for CL?
There are probably some engineers who have "dyslispia", to whom the normal example with the usual parentheses doesn't look any better than the messed up examples.
> He's right. Not just for Common Lisp, either. But I just can't kick lisp. And on the other side of that coin, if I think I will only ever develop a project on a small team, the nimbleness of Lisp helps. I feel like Lisp is custom built for high functioning, high quality, low-head-count teams. I kinda feel like most of my projects into this category.
The original linked article is a promotion for Julia with some decent criticisms of CL.
CL has a special place in my heart. I want to love it. I programmed in it, bought just about every book I could to learn about it, etc. But CL was just a huge time sink to do anything to me. Ignoring my (lack of) skill with it I found it was difficult to really find help (though the CL IRC channel is active and probably the nicest community in programming). Everything still felt strange to me 6 months in and even with other similarly good languages this usually doesn't occur. For example, with SBCL and Quicklisp it felt like even getting a database connection set up was pulling teeth.
I found the problem of having dozens of implementations hard to cope with as well. SBCL is a standard and it can be quick (sometimes as quick as C). But there's also Allegro, LispWorks, and several others. Each one of them has their own quirks you have to know. If I wanted to develop well on Windows you were pretty much siloed into LispWorks and the extremely high costs to use it. In fact, Allegro and LispWorks both make IDA Pro's pricing look reasonable and come close to rivaling some CAD software. Making SBCL work on Windows was kind of a pain but workable. But then you have to deal with the package managers like Quicklisp which are great...but so many extra steps to get stuff done. Finally, I found it extremely difficult to work with CL outside of Emacs (which I like a lot and am quite familiar) especially with my career forcing a choice between Sublime or Vscode.
I think CL should inspire similar languages. I fell in love with Clojure but dropped it because its community is dwindling. It's commercial uses have dwindled which made me sad. It also couldn't be compiled like other Lisps and lacked the "general purpose" feel to it (having chosen it's domain for concurrent programming I felt). I long for a popular, modernized lisp with all the benefits of the other lisps, except with a better package manager, a community united in a single direction, and lots of support.
(mito:connect-toplevel :sqlite3 :database-name "myapp") ;; also mysql and postgres
"having dozens of implementations hard to cope with"
=> you don't have to cope with so many, or any at all. Use one implementation if it suits you well.
"develop well on Windows"
=> SBCL works fine as of today.
"extremely difficult to work with CL outside of Emacs"
=> solved or being solved. See the plugins for Vim, Atom, Sublime, VSCode, Jupyter, Geany… and more. Also Portacle (a portable Emacs for CL), the Lem editor, the free LispWorks IDE…
https://lispcookbook.github.io/cl-cookbook/editor-support.ht...
"a better package manager"
=> Quicklisp is great (easy to use, easy smoothing) but with shortcomings. See CLPM, the Ultralisp distribution, Qlot.
"lots of support." there's room for anybody I guess: pro support (LispWorks, Allegro…), helpful community (reddit, Discord, pro mailing list…)
I used to be a religious devotee to Lisp, but then I started programming for a living and realized that static type systems (even of the simplest variety) are so helpful for preventing bugs. Unfortunately, I never found a typed variant of Lisp that I really liked; the uniform Lisp syntax doesn't collide well with the stratification of types and values.
I really like Racket and write as much as I can in Typed Racket. It's great but also has lots of weird little warts that I keep stumbling across. One of the biggest for me is that type signatures completely take the place of untyped Racket's contracts. It's way too difficult to have both types and contracts attached to things. Ideally one could write a contract for things that are not able to be expressed in the type system but Typed Racket doesn't let you.
Weakly typed languages are like that, once you start practicing programming as a team sport, the type system is your friend.
Another factor usually ignored, is that when coding as a team, code is read way more often than written/modified.
The success of a language has a lot to do on how easy it is to read code written by others, but without making readability too cumbersome to write for (e.g. AppleScript is easy to read, but hard to write).
Too much flexibility hampers understanding.
It's a balancing act, and in the end, a popularity contest.
The most widely used languages are likely best for most projects. There's a reason why they are successful.
> I recently read mfiano's blog post about Common Lisp. He makes the point that the language attracts loners. ...
That article by mfiano focuses on reasons the author switched from Lisp to Julia. As for the "loners" part, mfiano says this:
> This hints at a very real problem with Common Lisp: it has a tendency to attract people that employ the 'NIH syndrome' and refuse to collaborate. There is a large overlap of software available, and there is a large selection of low-hanging fruit for new developers. This coupled with the fact that the language is small, relatively speaking, hurts the language immensely.
Contrast this with how djha (TFA) puts it:
> I feel like Lisp is custom built for high functioning, high quality, low-head-count teams. I kinda feel like most of my projects into this category.
So TFA is kind of taking liberties with mfiano's post. I might come down to whether or not you view NIH syndrome as a net positive or negative.
There are multiple people who submit patches to free Lisp software. There are companies where Lisp people work together. There is free help offered in various Lisp fora.
Perhaps the author didn't receive free help on his own projects? Perhaps he didn't offer free help on other people's projects?
Lisp was my first real programming language many years ago and the superior environment of Lisp machines (which I had the chance to use from the mid-80s until the mid-90s) is what I still miss today. I dabbled with Fortran IV and Pascal too early on, a bit later with C too, at the same time, but I always found the need to manually manage storage (How long to plan a string? Or how large should an array be?) annoying.
Lisp offered me the freedom to think (and program) in abstract terms. As did (and does), BTW, Perl (especially with regexps, another great abstract tool), where memory management is nothing to care about unless your RAM isn't sufficient ;-0)
When I had to describe the different programming languages to lay people, I used to compare programming in Lisp with using a helicopter and a camera to collect data for map production, while other languages are similar to some earth bound vehicles and a tape measure. Yes, learning to fly a helicopter is more complicated than learning to drive (a bicycle or car) but once you managed the learning curve, you could produce much better results much faster (aka prototyping). And, of course, there are fewer pilots than drivers to chat with.
Here's an interesting (not so recent) talk by Peter Norvig where he summarized the Lisp perspective:
I had a similar experience, doing my first 3D graphics development on Symbolics Lisp Machines back in the 80's [1], and then spending my career developing 3D CG code in Fortran, C, and C++, missing the Lisp experience the whole time.
Some of the milestones along the way:
- Developed 3D animation software used to animate the dinosaurs in “Jurassic Park”.
- Software R&D lead for CGI feature “Final Fantasy: The Spirits Within”.
- Senior Software Developer on “The Hobbit” films.
Now I have returned to Common Lisp with a new open source 3D system [2], and it feels really good. As I deliberate over innumerable design decisions, the one thing I have told myself I will not consider changing is the choice of language. New, popular, hot, trendy languages (Clojure, Julia) may monopolize the conversation, but I am content with what Common Lisp allows me to do.
> I dabbled with Fortran IV and Pascal too early on, a bit later with C too, at the same time, but I always found the need to manually manage storage (How long to plan a string? Or how large should an array be?) annoying.
No need for planning storage most of the time in modern languages, even modern C++ you can just say
std::string s = "hello";
s += " world!";
And the string will automatically resize to fit "hello world!"
Well, I'm about to post perhaps the most controversial opinion anyone ever dared express about Lisp on this site, so maybe strap in and/or have a shot of the hard stuff:
It's a perfectly good programming language, but nothing special.
90 comments
[ 3.1 ms ] story [ 155 ms ] threadYou have REPL running, you can evaluate functions, and definitions on the fly, or completely re-define the function, even if said function is being called in a loop by other one, and it will work. Or, redefine classes, and it will be fine. Or, if function errors, debugger gets launched, and you have option to redefine variables, and the function will continue with that variable (if, you called undefined variable)
Though, I believe, its better to see how it works, rather than read, While the video is not mine, https://www.youtube.com/watch?v=6pMyhrDcMzw And remember, this is not hot reloading.
It is actually not about a "REPL" but "image-based" development. It is WAY ahead of the Python REPL, which was already a relief for me. The Python REPL is archaic in comparison.
There's probably a few different reasons to why but one thing that the 3 i've listed above all have in common is that they have image-based environments. So thats probably one of the top reasons why they have superior REPLs.
*many years writing python spaghetti to accomplish personal tasks, just now trying to build an employable foundation..
[0] http://www.paulgraham.com/avg.html (referenced in the top link blog post)
[1] http://community.schemewiki.org/?SICP-Solutions
(IE: assuming more Americans are in hn, you would want to pay at the American hours and get more votes over a shorter amount of time)
(I'm also confused who's enthusiastically upvoting 3-paragraph long text blogs).
- "Is that how submission ordering is done on HN?"
I have no idea, actually! There's clearly a pretty complicated algorithm behind it.
Personally I've never felt ashamed to roll up my sleeves and get some oil in my beard with C++. Perl, Python or really filthy Bash scripting.
But having learned Lisp (SICP) back in the 80s, It does sometimes feel like I took Latin and Greek at prep-school, and ever since have been slumming it with my working class coder pals.
Total geek snobbery for sure. But is there anything in it? Please say no.
From the outside, I didn't see any evidence of this. The people who were better and more experienced engineers tended to produce more, better, and faster. I saw that they were happier using the tools of choice. As far as I could tell the supposed extra productivity over Rails or some other modern framework for the task at hand was largely illusory.
On the one hand, happier engineers is good. On the other hand, code in relatively obscure languages is more difficult to find people to maintain and libraries are less likely to be kept patched by the community. It's a tradeoff. I wouldn't say one is better.
The primary gain from Lisp and similar is, in my experience, not that it's useful for writing software. It's that learning a different approach teaches you new ways to attack problems. Sometimes those are useful.
I don’t think the claim is that one has to use those older technologies exclusively. I read the GP’s comment as that the knowledge one gains by learning those older technologies gives one more tools in their bag when using modern technology.
> The primary gain from Lisp and similar is, in my experience, not that it's useful for writing software. It's that learning a different approach teaches you new ways to attack problems. Sometimes those are useful.
Exactly. An example: trying to learn functional programming? Erlang will force one into that mindset. It is natural to spot mapping, folding, flattening in any other language later on. Working with local data, or state machines becomes a habit because there’s no other way of doing things in Erlang. That knowledge is directly applicable in any other language.
This exactly describes my experience with Clojure. Sure, I learned “functional programming” which was a “different paradigm”, but at the end of the day what I learned was how to reason about:
1. Whether some bit of state is intrinsic, not strictly necessary but it is highly convenient, or neither of those. (The highly convenient case in Clojure is mostly a reference to Transients[1], which I don’t think I ever used. But the concept has been incredibly valuable when transferring what I’d learned to more imperative languages.)
2. There are correct and obvious function boundaries even for quite a lot of domain specific code, and they usually become evident while you work if the previous point is applied judiciously.
3. Some general purpose abstractions are so self-evidently useful that adopting them is never premature. (Here I’m thinking of what Clojure abstracts as `seq` and the enormous set of functions which operate on `seq`-conforming collection types. Other FP languages similarly bring their own wealth of broadly applicable abstractions.)
4. Many, maybe most, functions have a correct parameter order, which is the order you’d use for true lambdas. Once learned, it can almost always be applied subconsciously.
5. Metaprogramming is good, actually, if you have a good reason to do it. And having a good foundation for metaprogramming is invaluable even if its value is hard to elucidate. (Macros, of course. But I’d give my figurative left kidney to have time to explore the kinds of problems I could potentially solve with Racket’s language-oriented features.)
For another day or at least a later comment, 6. Lack of static types in most lisps hilariously undermines each one of the points above.
1: https://clojure.org/reference/transients
This knowledge is then applicable to any other programming language.
The syntax is just off-putting and I was never compelled to learn it in school, so I remain in algol-land. If time and focus allowed I'd do the SICP self study path but it's too easy to surf HN and Reddit instead.
SICP was first published in 1984 [2]
[1] https://stackoverflow.com/a/69261645/3414663
[2] https://en.wikipedia.org/wiki/Structure_and_Interpretation_o...
Philip Wadler's paper discussed and linked from here is also interesting: https://www.wisdomandwonder.com/link/1055/why-calculating-is...
For example SICP uses a so-called special form called cons-stream, which is in no Scheme definition (it was in no Scheme reports) and for which no implementation is given in the book, since it can't be implemented by the Scheme subset used for the book. It assumes that it is somehow provided without giving any details.
Related: https://srfi.schemers.org/srfi-216/srfi-216.html
Algol languages on the other hand, and math even on paper, are a rather wild bunch: they mix infix operators like plus/minus (1+1), multiplication/division (2*2), have postfix operators like faculty (9!) and even user defined functions f(x, y) in prefix notation. That should be way more off-putting theoretically, but people just grew up with this "mess" and think that fine.
Disclaimer: besides Lisp, I program in Perl or Shell too ;-)
You can write down the syntax of a call to a macro which has not yet been written, and Lisp will read it for you, giving you the tree. It just won't be able to expand it since the semantic definition through the macro name isn't there.
A conventional language will not do that. C++11 will not correctly read the shape of a C++17 phrase structure, and just complain about it not being defined.
Lisp macro can treat their arguments as a flat stream of tokens and parse them, even with LALR(1) parser generator or whatever. It's not not the typical way most macros are written. Macros leverage the ability of the invocation syntax to express pre-parsed nested structures.
Even if a macro goes crazy like that, it's still self-contained; the churning toilet bowl of parsing it has whipped up is cleanly confined within the boundaries of the macro call, which can be cleanly substituted anywhere an expression can be used.
Let me ask you a question: Are these two expressions equivalent?
and In the absence of macros, the answer is yes. In the presence of macros, there's no way of knowing until you've seen the definition of f: If f is a macro, they might not be. You need to know the syntax of the f macro to answer the question.CL has a core language with a simple syntax. It also has a lot of macros in its standard library.
Because it parses, I don't have to correct the author's punctuation, just their understanding of the necessary shape . The wrong shape they wrote has a coherent syntax, and we can discuss the problem in terms of differences in syntax, rather than differences between gibberish and valid syntax.
This is why your question is able to be, "are these expressions equivalent" and not "are these two token sequences equivalent".
I can write a TDD test case for a new macro that doesn't exist yet, which won't bring down the program.
E.g. (test (my:let (x 5) (+ x x)) 10)
This will catch the error that foo is not defined and report that the test case failed instead of returning the expected 42 value. The next test case in the same file can execute.
You couldn't have a test for new C++ or Java syntax, in a C++ or Java file being processed by a compiler that wasn't maintained in order to recognize the syntax. The file will blow up at parse time, not allowing any of the test cases in it to proceed.
> You need to know the syntax of the f macro to answer the question.
But I can see what that syntax is: it's (f (g x)) right there: i.e.
g and x are clumped together, so they have a closer relationship of some kind than either one has with f, at least syntactically. From looking at the use, I don't know whether it is valid shape for f, or if so, what other shapes are valid. And I don't know the semantics at all.I'm not struggling with syntax, though, just connecting it with a meaning.
That's not the definition of syntax.
If you're implementing a compiler, then you might use "syntax" as a shorthand for those parts of the syntax that are processed in a particular "syntax checking" stage of the compiler. But that's not all it is.
> From looking at the use, I don't know whether it is valid shape for f
That, on the other hand, is close to a definition of syntax: The rules for what has a valid shape and what doesn't.
The Lisp reader just reads s-expressions. It knows nothing about Lisp syntax and thus does not check it.
It was the reader which settled all the syntactic facts about what operation is applied to what operands.
At this point we could move the goalposts: even eval and compile don't know anything about Lisp syntax. Because, look: (- 10) and (- 10 1) behave differently: the 10 operand changes semantic role between minuend and subtrahend based on syntax. All that eval knows is that it's passing arguments.
A function can treat its argument values as the tokens of a syntax which are shored up according to arbitrary phrase structure rules, similarly to Unix programs like find and tcpdump.
The reader does not know anything about operands or operators. It does not even know if the thing read is data or code. It also does not know if it is valid code. Enter (3 4 *) and the reader happily takes that expression, not caring about operators and operands. The reader will print zero warning about such expressions.
To really know what the symbols in an expression actually are, one would need eval, compile or a code walker. Are they used as literal symbols? variables? functions? macros? block names? type names? ...
> Because, look: (- 10) and (- 10 1) behave differently: the 10 operand changes semantic role between minuend and subtrahend based on syntax. All that eval knows is that it's passing arguments.
That's semantics, not syntax. EVAL and COMPILE check that it is a function call, what arglist it expects (zero or more args, &optional args, &rest args, keyword args, and process them recursively. But Lisp has also special operators, macro operators and a lambda expression. There the syntax requirements are different or even context depended.
Also if Lisp reads the expression (foo * +), the reader does not know if FOO is a function or a macro, it thus does not know what * and + are. Are they variables? Are they operators? Something else? They don't even need to have a definition at read time.
The reader doesn't know about opperands or operators, but it certainly knows a symbol from an integer or string.
We've cleverly arranged things so that it doesn't have to know anything else in order to produce output that, in some cases, can be evaluated directly.
> That's semantics, not syntax
Syntax versus semantics is sometimes about where you put the goalposts.
What if I make it a macro that looks at the argument count and chooses a different function?
Is that still "semantics, not syntax"? Why does it become semantics if one function does it itself by switching on the argument count internally?Everything is syntax! Adding two floating-point value is syntax: the representation has to be parsed into sign bit, exponent and mantissa, and so on before anything happens.
Sure, those fields are accessed conveniently by position, but so are the elements of the list (sign exponent mantissa), and our goalposts say that that is syntax.
The parsing never stops. The CPU fetches instructions, decodes (== parsing!) and executes.
When the Lisp run-time looks at a value to extract the tag bits, and then further delves into the object accordingly, it is parsing syntax.
That's the s-expression syntax. Beyond that, symbols have no special meaning for the reader.
> Everything is syntax
Syntax describes the structure of a language.
Wikipedia: "the syntax of a computer language is the rules that defines the combinations of symbols that are considered to be correctly structured statements or expressions in that language."
CLHS 1.4.1.2 "This specification uses an extended Backus Normal Form (BNF) to describe the syntax of Common Lisp macro forms and special forms."
So the ANSI CL standard claims that its macro forms and special forms have syntax and that this syntax is described by an eBNF?
Example: DO
If I try this in SBCL It tells me that there is an error while parsing (!) arguments to the DEFMACRO DO.Woops? There is a parser? There is an error detected?
So, beyond the the reader SBCL seems to claim it has a parser?
But, above, the message "error while parsing arguments to DEFMACRO DO" is just a destructuring error on "macro lambda list" pattern match on a piece of datum: the unevaluated macro call. That pattern matcher has no idea what DO means, in much same way that the reader doesn't know what DO means.
The reader has done so much work, that the remaining parsing is just a very easy job which enjoys easy positional access to everything in the tree, at every level, at worst having to loop over a sequence of similar items.
The syntax notation like:
is relying on read syntax; it has parentheses in it! Moreover, it is a greatly condensed specification, due to its reliance on the properties of the read syntax.The init-form can be anything: a struct literal, vector, string, character. Yet, I don't see the rules for that in this grammar; they given elsewhere, and just assumed here.
If everything were to be made explicit here, the way a handful of parentheses have been made explicit, it would be pretty large specification, of which the do-specific parts above would be small.
The do-specific material is just a handful of self-contained patterns applied to individual objects that have already been parsed out by the reader.
For instance, the pattern pattern {var | (var [init-form [step-form]])}* is understood to be applied to that part of the form which can be conveniently retrieved as (cadr form). The amount of work left is very small, of low complexity. The pattern is not a recursive language; it is a regular language (if we pretend that var, init-form and step-form are terminal symbols).
When the (var init-form step-form) is matched against the corresponding object, the step-form is easily retrieved by position, regardless of the complexity of init-form. This is not true of the unparsed expression in which scanning across an init-form to get to step-form is a complex step. The reader has done the work which allows this stage of processing to treat "init-form" as if it were a terminal symbol in a simple regular grammar that is just concerned with a positionally-determined portion of the do syntax.
It looks like Lisp has a lot of syntax beyond the read syntax if we look at all of the forms together. They all share the read syntax, but have their unique bits that drive their shape. The more of them there are, the larger is the proportion of the language syntax that is contributed by those unique bits. Each time we write a macro and document it, the read syntax doesn't grow, but the amount of deep syntax does.
From the perspective of any one S-expression, though, the bulk of the specification of its syntax is in the domain of the reader. The pattern match for a specific shape like do is a small amount of easily digestible additional information.
'means' is in the domain of semantics. We talk about syntax: structure.
But what we know at this stage: DO is the CL:DO macro. So we know the actual operator, not just the symbol. We also know that it is a macro. We also know that the macro has a macro lambda list (CLHS 3.4.4 describes the syntax of those). We can now check the macro form against the actual CL:DO macro lambda list structure. And so on...
Thus we are now talking about the Lisp language, its operators and its structure requirements.
That's the purpose of a language parser: it has to know which symbols/identifiers are actual elements of the language, which operators these actually are and retrieves the syntactic constraints/rules for those.
In a language like Common Lisp (or similar dialects) this is 'difficult', since there are lexical bindings, compile-time side-effects and even lexical bindings to establish new syntax (macrolet).
> The syntax notation like: ... is relying on read syntax; it has parentheses in it!
It describes constraints on the structure (!) of valid Lisp forms. -> syntax!
See that the warnings talk about the actual Lisp operators and their requirements? We've left the domain of s-expression syntax here and talk about Lisp syntax requirements.None of these things are caught by the READER. All of these are actual warnings from SBCL.
> The init-form can be anything: a struct literal, vector, string, character. Yet, I don't see the rules for that in this grammar; they given elsewhere, and just assumed here.
The rules are in the language standard. The non-terminal INIT-FORM is specified to be a FORM. Which then is defined elsewhere.
> The init-form can be anything
Actually it can't. It must be a form (see the CLHS syntax description for DO). Thus ((foo)) for example is not valid, since it is not a valid Common Lisp form.
> From the perspective of any one S-expression, though, the bulk of the specification of its syntax is in the domain of the reader.
This is tautologic.
I was claiming that Lisp has structural constraints/rules on top (!) of s-expressions (and especially s-expressions as data) and gave the example of the Common Lisp standard, which describes these in an eBNF syntax.
Source code is actually simple nested list data. For example the following snippet, which binds two variables A and B, defines a local function and then calls this local function. This is just an s-expression, which consists of symbols, numbers and lists.
Even though s-expressions have a syntax with numbers, parentheses, symbols, ... - they can not only be written in a text editor, one can also directly compute with them using the typical Lisp operators for lists, numbers, strings, symbols, ...
The following enters literal data, which is not executed as code.
We can substitute the symbol * with the symbol expt in this s-expression - the variable * just holds the last evaluation result: Then this can be evaluated: Now, even though the program is written as a data structure, the Lisp program itself has syntax. Only function calls have the simple (operator args...) syntax. But there are also special forms and macro forms.LET is a syntactic form and expects a list of (variable value) pairs as its first sub-expression. Then follows a body of subexpressions, where the variables can be used.
FLET is a another syntactic form: it expects a list of function definitions and a body. The function definition starts with a name, followed by an arglist and then by a body.
In reality this is more complex, since LET and FLET allow declarations, functions allow complex argument lists, declarations, documentations, etc.
So for FLET the top-level EBNF syntax would be like:
With more syntax for lambda-list, declaration, documentation and form.(Yes, this is an exaggerated example but this really is the point. Also, static types. If there's anything of overarching regularity in what you type, it better be syntax or it will be hard to check and take advantage of. This is what Algol-style languages do for systems programming.)
This to me is actually the crisis for lisp. Any codebase requires you to learn all the various macros that do:
What is being discussed in this sub-thread is an oft-cited criticism of Lisp's "power" being too much for its own good, but rarely (if ever) a reflection of idiomatic practice.
Too much power for its own good is one thing, but being just not optimized for the common thing is another. How would the following function, randomly picked from what I have open on github right now, look in "Common" Lisp? And if I ask 3 persons to do it, what is the likelyhood that they would pick more-or-less the same approach?
It just uses names operators in prefix position. foo += 1 then is (incf foo). for (...) { ... } is then (loop for ... do ...). ...i32 counter = 0;... is then (let ((counter 0)) (declare (type i32 counter)) ...)
and so on...
Could you adorn this code with type declarations? Yes, and you'd get speed and/or safety benefits by doing so. Depends how much this function "matters" in the larger context.
But in your lisp version "next" and "edit" appear to be global methods. That seems like it could lead to a lot of naming collisions. Could you still have a document type that you can call "(edit document patch)" on? A preliminary Google search suggests that CLOS methods can't have definitions with a different number of parameters, so now you're stuck with a global "edit" method that can only ever take one parameter.
But, if generic functions aren't your thing, you can skip calling the functions even and just do
if you really want. This is basically Again, works for any object with a field named by the symbol "next". But it's idiomatic to just call the reader function you defined, since getters can be instrumented, debugged, traced, etc. Some people prefer to call the reader A-NEXT or B-NEXT, but it's not necessary.Regardless of all of that, symbols themselves belong to a namespace, called the "package". So your "edit" and my "edit" methods can have a different number of arguments without issue. (If they do have different arguments, they must mean different things.)
E.g. you might have some doc-model:edit symbol and file-utils:edit or whatever. The symbols have the same name, but are different objects in different packages.
The generic function doc-model:edit is therefore unrelated to file:edit.
Packaging at the symbol level means that the package system is relevant to anything whatsoever that is named by a symbol. If you have some logic programming system where you declare some named rules or facts, they can easily go into a package. Symbols which are quoted individually or in a larger literal are in the package system all the same; it's an independent layer.
After a while I realized it - I may have programmed in Common Lisp but never professionally... on a professional code base. Only ever on open source thingies, and small side projects. Paid or unpaid.
I've never seen industrial common lisp code. My last job had some but I never got to work on that project, and no longer can see it :(
Is there an example somewhere of this? C++ examples would be things like Chrome, C would be lots of open source OS's and kernel drivers. Is there something equivalent for CL?
I recently made a comment on this topic here:
https://news.ycombinator.com/item?id=33049170
There are probably some engineers who have "dyslispia", to whom the normal example with the usual parentheses doesn't look any better than the messed up examples.
The original linked article is a promotion for Julia with some decent criticisms of CL.
CL has a special place in my heart. I want to love it. I programmed in it, bought just about every book I could to learn about it, etc. But CL was just a huge time sink to do anything to me. Ignoring my (lack of) skill with it I found it was difficult to really find help (though the CL IRC channel is active and probably the nicest community in programming). Everything still felt strange to me 6 months in and even with other similarly good languages this usually doesn't occur. For example, with SBCL and Quicklisp it felt like even getting a database connection set up was pulling teeth.
I found the problem of having dozens of implementations hard to cope with as well. SBCL is a standard and it can be quick (sometimes as quick as C). But there's also Allegro, LispWorks, and several others. Each one of them has their own quirks you have to know. If I wanted to develop well on Windows you were pretty much siloed into LispWorks and the extremely high costs to use it. In fact, Allegro and LispWorks both make IDA Pro's pricing look reasonable and come close to rivaling some CAD software. Making SBCL work on Windows was kind of a pain but workable. But then you have to deal with the package managers like Quicklisp which are great...but so many extra steps to get stuff done. Finally, I found it extremely difficult to work with CL outside of Emacs (which I like a lot and am quite familiar) especially with my career forcing a choice between Sublime or Vscode.
I think CL should inspire similar languages. I fell in love with Clojure but dropped it because its community is dwindling. It's commercial uses have dwindled which made me sad. It also couldn't be compiled like other Lisps and lacked the "general purpose" feel to it (having chosen it's domain for concurrent programming I felt). I long for a popular, modernized lisp with all the benefits of the other lisps, except with a better package manager, a community united in a single direction, and lots of support.
"getting a database connection set up was pulling teeth." => https://lispcookbook.github.io/cl-cookbook/databases.html Use cl-dbi (connection layer) or Mito (light ORM) and do:
"having dozens of implementations hard to cope with" => you don't have to cope with so many, or any at all. Use one implementation if it suits you well."develop well on Windows" => SBCL works fine as of today.
"extremely difficult to work with CL outside of Emacs" => solved or being solved. See the plugins for Vim, Atom, Sublime, VSCode, Jupyter, Geany… and more. Also Portacle (a portable Emacs for CL), the Lem editor, the free LispWorks IDE… https://lispcookbook.github.io/cl-cookbook/editor-support.ht...
"a better package manager" => Quicklisp is great (easy to use, easy smoothing) but with shortcomings. See CLPM, the Ultralisp distribution, Qlot.
"lots of support." there's room for anybody I guess: pro support (LispWorks, Allegro…), helpful community (reddit, Discord, pro mailing list…)
You are going to miss it when coding in other languages.
Here's an example:
https://calva.io/paredit/
so you just switched religions ?
Another factor usually ignored, is that when coding as a team, code is read way more often than written/modified.
The success of a language has a lot to do on how easy it is to read code written by others, but without making readability too cumbersome to write for (e.g. AppleScript is easy to read, but hard to write).
Too much flexibility hampers understanding.
It's a balancing act, and in the end, a popularity contest.
The most widely used languages are likely best for most projects. There's a reason why they are successful.
1: https://github.com/carp-lang/Carp
That article by mfiano focuses on reasons the author switched from Lisp to Julia. As for the "loners" part, mfiano says this:
> This hints at a very real problem with Common Lisp: it has a tendency to attract people that employ the 'NIH syndrome' and refuse to collaborate. There is a large overlap of software available, and there is a large selection of low-hanging fruit for new developers. This coupled with the fact that the language is small, relatively speaking, hurts the language immensely.
Contrast this with how djha (TFA) puts it:
> I feel like Lisp is custom built for high functioning, high quality, low-head-count teams. I kinda feel like most of my projects into this category.
So TFA is kind of taking liberties with mfiano's post. I might come down to whether or not you view NIH syndrome as a net positive or negative.
There are multiple people who submit patches to free Lisp software. There are companies where Lisp people work together. There is free help offered in various Lisp fora.
Perhaps the author didn't receive free help on his own projects? Perhaps he didn't offer free help on other people's projects?
sounds like heaven
Lisp offered me the freedom to think (and program) in abstract terms. As did (and does), BTW, Perl (especially with regexps, another great abstract tool), where memory management is nothing to care about unless your RAM isn't sufficient ;-0)
When I had to describe the different programming languages to lay people, I used to compare programming in Lisp with using a helicopter and a camera to collect data for map production, while other languages are similar to some earth bound vehicles and a tape measure. Yes, learning to fly a helicopter is more complicated than learning to drive (a bicycle or car) but once you managed the learning curve, you could produce much better results much faster (aka prototyping). And, of course, there are fewer pilots than drivers to chat with.
Here's an interesting (not so recent) talk by Peter Norvig where he summarized the Lisp perspective:
"Lisp: Where Do We Come From? What Are We? Where Are We Going?" http://www.norvig.com/lisp_talk_final.htm
Some of the milestones along the way: - Developed 3D animation software used to animate the dinosaurs in “Jurassic Park”. - Software R&D lead for CGI feature “Final Fantasy: The Spirits Within”. - Senior Software Developer on “The Hobbit” films.
Now I have returned to Common Lisp with a new open source 3D system [2], and it feels really good. As I deliberate over innumerable design decisions, the one thing I have told myself I will not consider changing is the choice of language. New, popular, hot, trendy languages (Clojure, Julia) may monopolize the conversation, but I am content with what Common Lisp allows me to do.
[1]: https://kaveh808.medium.com/late-night-lisp-machine-hacking-... [2]: https://github.com/kaveh808/kons-9
I should re-read the Norvig paper...
No need for planning storage most of the time in modern languages, even modern C++ you can just say
And the string will automatically resize to fit "hello world!"It's a perfectly good programming language, but nothing special.