9 ms·
It's the lists. No, not the prefix notation, parenthesis, what have you, although that doesn't help. The lists themselves. In Lisp, code is data, and data is l
by samatman 3y ago
It's the lists.
No, not the prefix notation, parenthesis, what have you, although that doesn't help. The lists themselves. In Lisp, code is data, and data is lists.
Yes, of course, there are hashmaps, arrays, strings. But idiomatic Lisp code really does use linked lists extensively, it's an entire style of programming. Even if you'd prefer to use different data structures (and again, Common Lisp does support this), you still have to be able to read and reason about the many parts of the language designed for, as the old quip had it, prothething lithts.
The "weird looking syntax" does not help, but Lisp hackers are right that you get used to that part, might even appreciate it if macros are something you really care about. Structural editing a la paredit and parinfer are pretty nice too.
But when it comes with a weird way of programming, that's a bridge too far for a lot of people. It's harder to learn, read, and reason about, for longer than most languages.
I'm glad they mentioned Julia though. Learned everything Dylan had to teach and then some, real treat of a language, and, it turns out, writing macros without cons cells is rather pleasant and powerful. Certainly an acceptable Lisp in my book. Maybe someday they'll add conditions and restarts, that's the one feature CL which gives it an edge on Julia.
- IshKebab 3y agoI disagree. I have no problem with lists obsession in other languages like OCaml. The parentheses are just too much. I get how elegant and unambiguous they are for computers but I am not a computer. It's like RPN. It's elegant and easy for code to parse and unambiguous and all these nice things.... except it isn't easy for me to parse. Compilers are perfectly capable of compiling readable code like Rust so I don't see why I should have to do the tedious work of figuring out all the parentheses manually. Lisp is like a really great IR. But I don't want to program in an IR.
- JohnFen 3y ago> It's like RPN That's genuinely fascinating for me, because I find postfix notation to be more intuitive and easy to parse than infix notation.
- murderfs 3y agoIt's definitely easier to parse programatically, but IMO it's a nightmare to read as a human. e.g. compare these expressions: (x+y)^2 = x^2 + 2xy + y^2 x y + 2 ^ = x 2 ^ 2 x y * * y 2 ^ + + I find it nearly impossible to parse this without actually maintaining the stack in my mind.
- gumby 3y agoIt's exactly how you would do the calculation if you ran it by hand, unlike the regular algebraic notation. I am flabbergasted that anyone uses the latter, especially as for most of the history of mathematics calculation was done by hand!
- svachalek 3y agoI don't "run" x 2 ^ by hand and I don't think anyone does. Some people find different notations more intuitive and that's fine but whether it's prefix postfix or infix, it's still an abstraction.
- gumby 3y ago> I don't "run" x 2 ^ by hand and I don't think anyone does Of course everyone evaluates expressions symbolically: in code review, when debugging, even when looking over an expression just after typing it to make sure it’s right.
- SR2Z 3y agoMany languages have different verb order - English is notionally SVO (subject-verb-object), while Hindi is SOV. That is probably much more related to why we use prefix/infix/postfix notation: because it maps to our native languages better.
- deleted 3y ago[deleted]
- jjav 3y ago> I find it nearly impossible to parse this without actually maintaining the stack in my mind. You don't write out RPN in a long sequence like that. I'm a devoted RPN advocate and seeing "x y + 2 ^ = x 2 ^ 2 x y * * y 2 ^ + +" is indeed difficult to parse. But that's not at all how you use RPN. You compute the problem going from smaller units to larger units getting intermediate results as you go and combining them. You'd never write out the sequence as you did and try to follow it after the fact.
- Zak 3y ago> I don't see why I should have to do the tedious work of figuring out all the parentheses manually. I think most people who write a lot of Lisp don't do that. I use Paredit mode in Emacs, which doesn't allow the parens to become mismatched and has operations like "move the last token out of this expression" and "jump to the next expression" so it actually feels like you're editing a tree rather than a chunk of text. A quick search says there's an equivalent for VSCode, and I'm sure other editors have options as well. I've seen some amount of structural editing for languages that don't use s-expressions, but it's always pretty limited. If you've written Lisp with a good structural editor and still don't like editing a program as a tree, then we think about code very differently.
- HexDecOctBin 3y ago> operations like "move the last token out of this expression" and "jump to the next expression" And that is the problem. When I am writing code, last thing I want to do is to think about tree manipulation and the numerous commands to do similar-but-not-the-same operations. This is the same reason I don't like Vim, it's hard enough to keep the problem domain in mind, there's no mental space to also remember the ed-style editing commands.
- packetlost 3y agoAfter the learning period it becomes muscle memory and you don't have to think about it. If you're not willing to put in even the small amount of effort to build up new muscle memory, I don't know what to say other than don't be so defeatist.
- Zak 3y agoThis must be where we think about code very differently. Most code has a tree structure, though many put the root token outside the brackets that mark it. Languages like C, Java and Rust use brackets to express their tree structure much like Lisp does, though they use more flavors of bracket than Common Lisp. Python uses indentation, but it's still expressing a tree. Ruby, Lua, BASIC, and others add keywords, but the stuff inside `do ... end` is still a branch of a tree. Without a structural editor, I'm still building a tree, but I have to keep more of it in my head.
- lilactown 3y agoI think it's as much about familiarity than anything else. I've programmed full time in Clojure for the last 6 years, and I find it just as easy to read than other languages I'm familiar with (JavaScript, Java) and way less easy to read than other languages like Haskell or OCaml that have their own syntax lineage. I'm sure if I spent an amount of time becoming familiar with them, I'd find them just as easy as I do Lisp. There are certain things that languages can do that make syntax easier; for instance, Clojure's default constructs reduce a lot of parens compared to CL by using brackets [] and removing nesting. For instance ;; clojure (defn sum-and-square [a b] (let [sum (+ a b)] (* sum sum))) ;; common lisp (defun sum-and-squaer (a b) (let ((sum (+ a b)) (* sum sum))) This leads some people to assert (with other reasons too) that Clojure is not in fact a "lisp."
- lispm 3y ago;; common lisp (defun sum-and-square (a b &aux (sum (+ a b)) (* sum sum))
- erichocean 3y ago;; clojure (defn sum-and-square [a b] (let [sum (+ a b)] (* sum sum))) Honestly, I'm not sure what you're trying to show.
- lispm 3y agoI'm not sure what you are trying to show? Code can be in one line? Okaaaaay... I was trying to show that one can define local variables without adding another list level via let. Like if one would/could write in Clojure: (defn sum-and-square [a b &blah sum (+ a b)] (* sum sum)) Another Common Lisp example, we'll stick to your one liner format, using infix syntax via a reader macro: (defun sum-and-square (a b) #I(sum=a+b,sum*sum)) Before you ask: what am I trying to show? This shows that Common Lisp syntax can be deeply extended by the user and that this is a standard feature of the language. Want a shorter infix notation without s-expressions? Why not...
- brlewis 3y agoWith Rust do you close all your } manually? Do you have an example of Rust code where the equivalent Lisp code would be harder to parse, assuming equal familiarity with both languages?
- HexDecOctBin 3y ago> With Rust do you close all your } manually? You don't have to, because you don't end up with }}}}}}}}} in Rust, because statements end with ; and not }.
- brlewis 3y agoI didn't end up with ))))))))) in Scheme because it was easy to create smaller functions. I do see rust ending with } } } In TypeScript I'll often find myself ending blocks of code with }); } }); } I'm having fun with TS, but I do miss the parentheses. They were simpler.
- HexDecOctBin 3y ago1. Lots of small functions lead to code in which bugs often hide in plain sight, see this old memo by John Carmack: http://number-none.com/blow/john_carmack_on_inlined_code.html http://number-none.com/blow/john_carmack_on_inlined_code.htm... 2. Scheme is better than Common Lisp when it comes to Lots of Irritating Superfluous Parenthesis, since declaring a variable in Scheme (using 'define') doesn't create a new nesting like it does in Common Lisp (using 'let').
- IshKebab 3y agoI do, but there are about 4x fewer }'s in Rust than there are )'s in Lisp. Look at these: https://rosettacode.org/wiki/Archimedean_spiral#Common_Lisp https://rosettacode.org/wiki/Archimedean_spiral#Common_Lisp https://rosettacode.org/wiki/Archimedean_spiral#Rust https://rosettacode.org/wiki/Archimedean_spiral#Rust
- Karellen 3y ago> But idiomatic Lisp code really does use linked lists extensively, Idiomatic python code uses lists extensively. In my (somewhat limited) experience, they're the default data structure to use for a lot of algorithms. Slightly more accurately, a lot of stuff in python uses sequences extensively, which are implemented by a number of types - but the default sequence is a list. And strings are lists. Yeah, if a list doesn't cut it then try sets or tuples or generators or coroutines or something else that implements a sequence. But for your initial prototype? List. And python's pretty popular.
- edflsafoiewq 3y agoA Python list is an ArrayList/vector, not a linked list. CL lists are linked lists of cons cells.
- samatman 3y agoCorrect. There's no such thing as a python a-list, or p-list, and they can't share structure. There's overlap between programming in Python with lists, and programming in Lisp with lists, but not as much as it appears. In idiomatic Lisp, it's rather common to search a list for a value, cdr it, and cons that cdr to the collection you're building up. Python has nothing like that, because what I said makes no sense in terms of Python lists.
- o11c 3y agoWhat do a-list/p-list have to do with sharing structure? If you want shared structure of lists in Python, use `itertools.chain`. If you want key-value mappings, use a dict. Order is preserved by default nowadays, and conversion to/from an iterable of 2-tuples is trivial.
- thaumasiotes 3y ago> If you want shared structure of lists in Python, use `itertools.chain`. That would involve zero shared structure. itertools.chain is just an iterator that iterates one iterable and then, instead of declaring that it's done, goes on to iterate another one. Shared structure between lists means that two lists refer to some of the same regions of memory.
- taeric 3y agoI'm trying to understand why lists matter. Yes, there are some oddities where some base constructs don't play as well with hashmaps as they do with lists, but it is far from difficult to work with a hashmap. Or an array. What difficulty are you talking about, specifically? (Genuine question.) Biggest difference I know of, off the top of my head, is the LOOP macro having constructs that care which one you have. Curious if there is more.
- QuadmasterXLII 3y agoO(1) access to the nth item turns out to be more important than O(1) insertion.
- taeric 3y agoYou'd be surprised how many exponential loops are in successfully run stacks. :) But yes, when it matters, it matters. Luckily, it isn't hard to use appropriate data structures. Also luckily, a surprising amount of code will linearly process data.
- cwicklein 3y agoI had the same thought about conditions a few days ago and discovered this: https://discourse.julialang.org/t/ann-package-conditions-jl/104863 https://discourse.julialang.org/t/ann-package-conditions-jl/...
- valty 3y agoIt's funny that behind the scenes in all languages, everything is lists (in the AST). It's strange that we tend to initially code in easily readable built-in control structures (if, switch, for, while), but then there always comes a point when you need to refactor...and convert them into lists of data structures and process those. For example, if you are matching routes in a web server, you can write a bunch of if-statements. Very simple. Easily understandable. And you can use whatever criteria you want, in whatever order you want. if (request.pathname == '/foo') { return foo } if (request.pathname == '/bar') { return bar } But say now you want to print a list of all the routes and how they are matched. Most people create a concept of a `Route` object that contains a predicate, and then you loop over those in a list. It feels super clean. But now if you want an exotic way of matching a particular route, or you want route priority or anything like that, and your route matcher and Route objects start becoming really complex...but if you did it with a simple code block with if statements, it would have been really easy. const routes = [ {pathname: '/foo', action: () => {}}, {pathname: '/bar', action: () => {}}, ] for (const route in routes) { if (route.pathname === request.pathname) { return route.action } } If we could have referenced our if-statement code blocks as a list (using Reflection or something), then we could have avoided any abstraction and stayed totally flexible. I could easily throw a few more requirements at you, and you would quickly have some frankenstein Route object and matching logic. It's this weird process of "dont-repeat-yourself" where everything looks the same and you abstract it, and then you realize its not all the same, and instead of back-tracking the data structure, you just tack on new stuff and more complicated logic. Complexity in software stems from these pre-mature abstractions.
- tangentstorm 3y agoYes but in whatever language you're imagining here, a "list" probably isn't the kind of list that lisp uses (linked lists of cons cells).
- skybrian 3y agoIt's not true that everything is a list in most languages. Typically an AST is built with both lists and structs. Special forms are always going to exist and the code needs to deal with them, so this isn't a loss; it's being more explicit. Well-named fields are better documentation and faster than accessing the nth item than a linked list. (You can emulate named fields using just lists, but it's just a convention.) Also, in most languages, the lists aren't linked lists. They're typically array-backed. JSON has both lists and objects (which serve as structs) and it's quite popular and usable for representing AST's.
- jjav 3y agoI didn't see mentioned in the thread (maybe missed it) the #1 reason, IMO, that Lisp can't be popular in companies. Everyone should take the time to learn Lisp and do a handful of personal projects with it. It'll help your growth as a software engineer. Just do it! But that doesn't make it a great corporate language. Lips is infinitely flexible, you can mutate it to be what you want. That's cool and feels awesome. Also: a maintenance nightmare as soon as you have more than ~1 person working on the codebase! Everyone surely has heard the joke/truism how C++ can be great as long as you only use a sane subset. But every team uses a different subset. Now imagine something like Lisp where every developer & team morphs it in a different way and you have a product with hundreds of developers on it. Try maintaining that without going insane. The anti-Lisp is something like Go. Simple, not very flexible, everyone does it the same way mostly, you can plug & play developers like scrum demands we do.
- fl0ki 3y ago> The anti-Lisp is something like Go. Simple, not very flexible, everyone does it the same way mostly, you can plug & play developers like scrum demands we do. This has not been my experience with Go. The only areas where Go actually enforces consistency is that gofmt has no configuration surface, and Go Modules conclusively won the dependency management war. Other than that, it's still a lawless wasteland. Foreword: Go is not bad, far from it. It does a lot of things right and you can do a lot worse. But myths around its simplicity, idioms, best practices, etc. have created a generation of monstrous tech debt hiding behind tidy syntax. One of the worst myths is that it's easy to see the right way to do something in Go, because now when everyone does things their own batshit ways, they each think they did it the obviously right way. * Go has no build system, and you'll need one for anything but the most trivial projects. Many use `make`, but some people insist that `just`, `ninja`, etc. are worth people having to install extra dependencies. The worst is when projects use straight shell scripts, having all of the platform compatibility problems of make with none of the benefits. * Go has no macros. Do you use reflection, code generation, or write everything out by hand. You can't even parse some JSON without being forced to make a choice here. (The standard library option, `encoding/json`, is the worst by far. It's also the most popular because of course it is.) * Even when you're done with that, you still have to choose a way to validate that input, because e.g. Go doesn't even have a concept of a value being "required" so you'll be introducing that somehow. The "validation" frameworks (sigh) that have an opinion on this still do it inconsistently for different builtin types, and often silently do nothing for custom types. Good luck being consistent even within a project let alone between projects and teams. * Go generics are very limited, and how you work around those limitations can vary greatly, e.g. do you use receiverless methods to simulate associated constants/functions or do you go back to reflection for those. There's still no solution for associated types, you either make do without them or abandon the type system altogether and use reflection. (In a LISP thread it might be hard to imagine just how bad this can get, because there's dynamically typed in a language that's built for it, and there's dynamically typed in a language pretending to be statically typed) * Channels, mutexes, and atomics all have their place, but most people never understand their tradeoffs properly (and community myths do more harm than good), so not only do they architecture projects around different options, but often the wrong options for their requirements. People will make performance arguments with no measurements, and correctness arguments with no proofs, etc. and when you join an existing project you'll be sifting through its uniquely crafted wreckage to figure out what invariants can even begin to hold. * The Go community has a lot of vitriol against testing frameworks, but Go has no useful functions for comparing values of non-trivial types, so you either use a test framework after all or reinvent one badly. You can't even define equality in Go recursively through pointer, slice, or map types -- strings compare by contents, pointers compare by address which is almost never useful, and maps and slices cannot be compared at all. It's not even consistent among builtin types. It's very common to use reflection here, see above, this is its own special hell. * Go has no sum types, pattern matching, or enums. (No, multiple value returns are not enums, they are not a type you can use as a map key, they cannot nest, etc). You'll be reinventing these somehow with structs and interfaces and switches which have to choose to panic or ignore unexpected branches. Many projects, knowingly or not, create unenforceable sum types where different records use different subsets of fields of a struct. It makes C look modern and high-level, because at least C has unions. I could go on. Go gets far more credit here than it deserves. Again, it does a lot right and it can be used well, but you should not expect quality or consistency from a broad range of programmers just because it's Go. Management that banks heavily on this myth generally pays for it dearly.
- CodeWriter23 3y ago+1 for writing “processing lists” with a lisp
- habibur 3y agoIt's vector and hashmap that you need most, not linked list.
- breather 3y agoI'm tackling a significant lisp project right now, and the thing that holds me up right now is that the code is difficult to organize. Python, java, rust, go etc have well-defined patterns to figure out where code lives and where you might expect certain behaviors to occur. With lisp you can really shoot yourself in the foot very easily by using abstractions that are difficult to follow and are spread out across many files.
- freilanzer 3y agoIf there's one language in existence where you can shoot yourself in the foot using abstractions it's Java. It produces spider webs of dependencies, hierarchies, etc.
- breather 3y agoRight, but that's predictable and straightforward to manage. Lisp is far less structured, meaning there are far fewer guidelines or clear indications of intent built into the organization of the program. Or at least, there's enough complexity it's much more difficult to internalize than Java, even with all its verbose warts.
- vindarel 3y ago> the code is difficult to organize we can simply do `(defpackage :mypackage (:use :cl))` then `(in-package :mypackage)`. Since they don't have to follow file hierarchy, we are free. That's good for the long term, and general flexibility. But it's different, sure. Good luck!
- breather 3y agoI mean, separating files (or modules or libraries or whatever) seems somewhat orthogonal to my point—where you expect to find behavior, and what behavior you expect, rather depends on how you use lisp.
- mark_l_watson 3y agoI like to compose programs from small one purpose Common Lisp libraries, each is a Quicklisp project. I used to write larger monolith CL projects, but I like my newer approach better. Off topic, but I take the same approach for my personal Python and Racket projects also.
- funcDropShadow 3y ago> It's the lists. That is one of the defining differences between Clojure and older Lisps. Clojure has list, vectors, maps, and sets as equally well supported data structures in the syntax and standard library. Classical Lisps often miss proper abstractions over different data structures. Clojure has those as well.
- naveen99 3y agoMore specifically, the linked lists are too inefficient for modern computing to be the default data structure. Not enough locality, too much jumping around memory. Reality is a tree, but to organize it, you have to convert it into arrays, maps, and matrix multiplication.
- elviejo 3y agoReality is a graph. We understand it as a multi-tree. But we write it in lists.
- CyberDildonics 3y agoThe reality of a computer is an array since memory is sequential addresses and programs deal with data in memory.
- CyberDildonics 3y agoI think it's strange that anyone would even ask why common lisp isn't popular. There is no payoff to writing something in it. The binaries are huge, it is going to be a lot less clear than modern C++, it won't be as fast, you still have a garbage collector, the libraries are niche, the syntax is reversed, the tools are niche, the ecosystem is niche, and everything you do is the opposite of what is intuitive at first. Then on top of all this is built on linked lists which are essentially an obsolete data structure in their simplest form. There is no reason to learn something with backwards syntax and ancient tools when there isn't even any payoff. Write something in C and the program is fast, small, native and can be compiled anywhere in a fraction of a second. There is still a payoff for all the very real pain. In lisp there is just no reason to use it from any angle other than how clever someone can be with macros and that is the exact opposite of good sustainable programming.
- packetlost 3y agoI don't think it's the lists. I think it's the cons cells. Lists are pretty ergonomic in most languages. As someone who loves Lisp (Scheme, specifically), cons cells are elegant for the compiler nerds and a complete nightmare for the programmer. Fuck `car`, `cdr`, `cdar`, `cddar`, `caar`, etc.. It's a horrible interface and every time I need to remember which variant gets me the value stored at the tail of a list I get a little more angry and want to write more Clojure and less Scheme.