11 ms·
Good article. Funnily enough the throw away line "I don't see parentheses anymore". Is my greatest deterrent with lisp. It's not the parens persay, it's the fac
by discmonkey 1y ago
Good article. Funnily enough the throw away line "I don't see parentheses anymore". Is my greatest deterrent with lisp. It's not the parens persay, it's the fact that I'm used to reading up to down and left to right. Lisp without something like the clojure macro ->, means that I am reading from right to left, bottom to top - from inside out.
If i programmed enough in lisp I think my brain would adjust to this, but it's almost like I can't full appreciate the language because it reads in the "wrong order".
- whalesalad 1y agoThis is the most elementary hurdle a lisp programmer will face. You do indeed become adjusted to it quite quickly. I wouldn’t let this deter you from exploring something like Clojure more deeply.
- eadmund 1y ago> It's not the parens persay, it's the fact that I'm used to reading up to down and left to right. Lisp without something like the clojure macro ->, means that I am reading from right to left, bottom to top - from inside out. I’m not certain how true that really is. This: foo(bar(x), quux(y), z); looks pretty much identical to: (foo (bar x) (quux y) z) And of course if you want to assign them all to variables: int bar_x = bar(x); char quux_y = quux(y); return foo(bar_x, quux_y, z); is pretty much the same as: (let ((bar-x (bar x)) (quux-y (quux y))) (foo bar-x quux-y z)) FWIW, ‘per se’ comes from the Latin for ‘by itself.’
- kibwen 1y agoThe tragedy of Lisp is that postfix-esque method notation just plain looks better, especially for people with the expectation of reading left-to-right. let bar_x = x.bar() let quux_y = y.quux() return (bar_x, quux_y, z).foo()
- tmtvl 1y agoDe gustibus non disputandum est, I personally find the C++/Java/Rust/... style postfix notation (foo.bar()) to be appalling.
- kazinator 1y agoTXR Lisp has this notation, combined with Lisp parethesis placement. Tather than obj.f(a, b). we have obj.(f a b). 1> (defstruct dog () (:method bark (self) (put-line "Woof!"))) #<struct-type dog> 2> (let ((d (new dog))) d.(bark)) Woof! t The dot notation is more restricted than in mainstream languages, and has a strict correspondence to underlying Lisp syntax, with read-print consistency. 3> '(qref a b c (d) e f) a.b.c.(d).e.f Cannot have a number in there; that won't go to dot notation: 4> '(qref a b 3 (d) e f) (qref a b 3 (d) e f) Chains of dot method calls work, by the way: 1> (defstruct circular () val (:method next (self) self)) #<struct-type circular> 2> (new circular val 42) #S(circular val 42) 3> *2.(next).(next).(next).(next).val 42 There must not be whitespace around the dot, though; you simply canot split this across lines. In other words: *2.(next) .(next) ;; nope! .(next) ;; what did I say? The "null safe" dot is .? The following check obj for nil; if so, they yield nil rather than trying to access the object or call a method: obj.?slot obj.?(method arg ...)
- ssivark 1y agoAnd what about when `bar` takes several inputs? Postfix seems like an ugly hack that hyper-fixates on functions of a single argument to the detriment of everything else.
- kibwen 1y agoLook at the last line in the example, where I show a method being called on a tuple. Postfix syntax isn't limited to methods that take a single argument.
- int_19h 1y agoIt's not like postfix replaces everything else. You can still do foo(bar, baz) where that makes the most sense. However, experience shows that functions having one "special" argument that basically corresponds to grammatical subject in natural languages is such a common case that it makes sense for PLs to have syntactic sugar for it.
- wk_end 1y agoLooks better is subjective, but it has its advantages both for actual autocomplete - as soon as I hit the dot key my IDE can tell me the useful operations for the obejct - and also for "mental autocomplete" - I know exactly where to look to find useful operations on the particular object because they're organized "underneath" it in the conceptual hierarchy. In Lisps (or other languages/codebases that aren't structured in a non-OOP-ish way) this is often a pain point for me, especially when I'm first trying to make my way into some code/library. As a bit of a digression: The ML languages, as with most things, get this (mostly) right, in that by convention types are encapsulated in modules that know how to operate on them - although I can't help but think there ought to be more than convention enforcing that, at the language level. There is the problem that it's unclear - if you can Frobnicate a Foo and a Baz together to make a Bar, is that an operation on Foos, on Bazes, or on Bars? Or maybe you want a separate Frobnicator to do it? (Pure) OOP languages force you to make an arbitrary choice, Lisp and co. just kind of shrug, the ML languages let you take your take your pick, for better or worse.
- CyberDildonics 1y agoIt's not really subjective because people have had the opportunity to program in the nested 'read from the inside out' style of lisp for 50 years and almost no one does it.
- no_wizard 1y agoI think the cost of Lisp machines was the determining factor. Had it been ported to more operating systems earlier history could be different right now.
- CyberDildonics 1y agoThat was 40 years ago. If people wanted to program inside out with lots of nesting then unfold it in their head, they would have done it at some point a long time ago. It just isn't how people want to work. People don't work in postfix notation either, even though it would be more direct to parse. What people feel is clearer is much more important.
- all2 1y agoThe lisp is harder to read, for me. The first double paren is confusing. (let (bar-x (bar x)) (quux-y (quux y))) (foo bar-x quux-y z) Why is the second set of parens necessary? The nesting makes sense to an interpreter, I'm sure, but it doesn't make sense to me. Is each top-level set of parens a 'statement' that executes? Or does everything have to be embedded in a single list? This is all semantics, but for my python-addled brain these are the things I get stuck on.
- jasbrg 1y ago> Why is the second set of parens necessary? it distinguishes the bindings from the body. strictly speaking there's a more direct translation using `setq` which is more analogous to variable assignment in C/Python than the `let` binding, but `let` is idiomatic in lisps and closures in C/Python aren't really distinguished from functions.
- eadmund 1y agoYou’re right! (let (bar-x quux-y) (setq bar-x (bar-x) quux-y (quux y)) (foo bar-x quux-y z)) I just wouldn’t normally write it that way.
- MayeulC 1y agoI am not a Lisp expert by any stretch, but let's clarify a few things: 1. Just for the sake of other readers, we agree that the code you quoted does not compile, right? 2. `let` is analogous to a scope in other languages (an extra set of {} in C), I like using it to keep my variables in the local scope. 3. `let` is structured much like other function calls. Here the first argument is a list of assignments, hence the first double parenthesis (you can declare without assigning,in which case the double parenthesis disappears since it's a list of variables, or `(variable value)` pairs). 4. The rest of the `let` arguments can be seen as the body of the scope, you can put any number of statements there. Usually these are function calls, so (func args) and it is parenthesis time again. I get that the parenthesis can get confusing, especially at first. One adjusts quickly though, using proper indentation helps. I mostly know lisp trough guix, and... SKILL, which is a proprietary derivative from Cadence, they added a few things like inline math, SI suffixes (I like that one), and... C "calling convention", which I just find weird: the compiler interprets foo(something) as (foo something). As I understand it, this just moves the opening parenthesis before the preceding word prior to evaluation, if there is no space before it. I don't particularly like it, as that messes with my C instincts, respectively when it comes to spotting the scope. I find the syntax more convoluted with it, so harder to parse (not everything is a function, so parenthesis placement becomes arbitrary): let( (bar-x(bar(x)) quux-y(quux(y))) foo(bar-x quux-y z) )
- AdieuToLogic 1y agoOne of the awesome things about LISP is it encourages a developer to think of programs as an AST[0]. One of the things that sucks about LISP is - master it and every programming language is nothing more than an AST[0]. :-D 0 - https://en.wikipedia.org/wiki/Abstract_syntax_tree https://en.wikipedia.org/wiki/Abstract_syntax_tree
- almostgotcaught 1y ago> encourages a developer to think of programs as an AST can you imagine saying something like > The fradlis language encourages your average reader to think of essays as syntax [instead of content]. and thinking it reflects well on the language................
- fc417fc802 1y agoI think an alternative to paragraphs or some other organizational unit would be a more appropriate analogy. The AST aspect of Lisps is absolutely an advantage. It obviates the need for the vast majority of syntax and enables very easy metaprogramming.
- AdieuToLogic 1y agocan you imagine saying something like > The fradlis language encourages your average reader to think of essays as syntax [instead of content]. and thinking it reflects well on the language A reciprocating saw[0] is a great tool to have. It can be used to manipulate drywall, cut holes in various material, and generally allow "freehand cutting." But it is not the right tool for making measured, repeatable, cuts. It is not the right tool for making perfect right-angle cuts, such as what is needed for framing walls. In other words, use the right tool for the job. If a problem is not best expressed with an AST mindset, LISP might not be the right tool for that job. But this is a statement about the job, not about the tool. 0 - https://en.wikipedia.org/wiki/Reciprocating_saw https://en.wikipedia.org/wiki/Reciprocating_saw
- MarceColl 1y agoI think it really depends, in Common Lisp for example I don't think that's the case: (progn (do-something) (do-something-else) (do-a-third-thing)) The only case where it's a bit different and took some time for me to adjust was that adding bindings adds an indent level. (let ((a 12) (b 14)) (do-something a) (do-something-else b) (setf b (do-third-thing a b))) It's still mostly top-bottom, left to right. Clojure is quite a bit different, but it's not a property of lisps itself I'd say. I have a hard time coming up with examples usually so I'm open to examples of being wrong here.
- fc417fc802 1y agoYour example isn't a very functional code style though so I don't know that I'd consider it to be idiomatic. Generally code written in a functional style ends up indented many layers deep. Below is a quick (and quite tame) example from one of the introductory guides for Racket. My code often ends up much deeper. Consider what it would look like if one of the cond branches contained a nested cond. (define (start request) (define a-blog (cond [(can-parse-post? (request-bindings request)) (cons (parse-post (request-bindings request)) BLOG)] [else BLOG])) (render-blog-page a-blog request)) https://docs.racket-lang.org/continue/index.html https://docs.racket-lang.org/continue/index.html
- MarceColl 1y agoCommon Lisp, which is what I use, is not really a functional oriented language. I'd say the above is okay in CL.
- fc417fc802 1y agoI must have missed that memo. Sure it's remarkably flexible and simultaneously accommodates other approaches, but most of the code I see in the wild leans fairly heavily into a functional style. I posted a CL link in an adjacent comment. Here's an example that mixes in a decent amount of procedural code that I'd consider idiomatic. https://github.com/ghollisjr/cl-ana/blob/master/hdf-table/hdf-table.lisp https://github.com/ghollisjr/cl-ana/blob/master/hdf-table/hd...
- buttercraft 1y ago> reading from right to left, bottom to top - from inside out I don't understand why you think this. Can you give an example?
- fc417fc802 1y agoDoes this example help? https://github.com/ghollisjr/cl-ana/blob/master/binary-tree/binary-tree.lisp#L89 https://github.com/ghollisjr/cl-ana/blob/master/binary-tree/...
- kazinator 1y ago(log (sqrt (sin (* 2 pi x)))
- freehorse 1y agolog (sqrt (sin (2 * pi * x))) Seems as much right to left to me as the original one. And just 2 deletions (you missed closing the opening parenthesis) and 2 insertions.
- kazinator 1y agoRight. The ergonomic problem people face is that the chaining of functions appears in other contexts, like basic OOP. Some kids trained on banana.monkey().vine().jungle() go into a tizzy when they see (jungle (vine (monkey banana)))).
- freehorse 1y agoNot sure of other lisps, but clojure has piping. I was under the impression in general that composing functions is pretty standard in FP. For example the above can be written: (-> (* 2 PI x) sin sqrt log) Also while `comp` in clojure is right to left, it is easy to define one left to right. And if anything, it even uses less parentheses than the OOP example, O(1) vs O(n).
- jdougan 1y ago
- Uhhrrr 1y agoper se
- pfdietz 1y agoIt's easy enough to add -> (and related arrow operators) to Common Lisp as macros. https://github.com/hipeta/arrow-macros https://github.com/hipeta/arrow-macros The common complaint that Common Lisp lacks some feature is often addressed by noting how easy it is to add that feature.
- tmtvl 1y agoBesides arrow-macros there's also cl-arrows, which is basically exactly the same thing, and Serapeum also has arrow macros (though the -> macro in Serapeum is for type definitions, the Clojure-style arrow macro is hence relegated to ~>).
- drob518 1y agoBeen programming in Lisp for a while. The parents disappear very quickly. One trick to accelerate it is to use a good editor with structural editing (e.g., paredit in Emacs or something similar). All you editing is done on balanced expressions. When you type “(“, the editor automatically inserts “)” with your cursor right in between. If you try to delete a “)”, the editor ignores you until you delete everything inside and the “(“. Basically, you start editing at the expression level, not so much at the character or even line level. You just notice the indentation/shape of the code, but you never spend time counting parentheses or trying to balance anything. Everything is balanced all the time and you just write code.
- AdieuToLogic 1y ago> Good article. Funnily enough the throw away line "I don't see parentheses anymore". Is my greatest deterrent with lisp. It's not the parens persay, it's the fact that I'm used to reading up to down and left to right. Language shapes the way we think, and determines what we can think about. - Benjamin Lee Whorf[0] From the comments in the post: Ask a C programmer to write factorial and you will likely get something like this (excuse the underbars, they are there because blogger doesn't format code in comments): int factorial (int x) { if (x == 0) return 1; else return x * factorial (x - 1); } And the Lisp programmer will give you: (defun factorial (x) (if (zerop x) 1 (* x (factorial (- x 1))))) Let's see how we can get from the LISP version to something akin to the C version. First, let's "modernize" the LISP version by replacing parentheses with "curly braces" and add some commas and newlines just for fun: { defun factorial { x }, { if { zerop x }, 1 { *, x { factorial { - { x, 1 } } } } } } This kinda looks like a JSON object. Let's make it into one and add some assumed labels while we're at it. { "defun" : { "factorial" : { "argument" : "x" }, "body" : { "if" : { "zerop" : "x" }, "then" : "1", "else" : { "*" : { "lhs" : "x", "rhs" : { "factorial" : { "-" : { "lhs" : "x", "rhs" : "1" } } } } } } } } Now, if we replace "defun" with the return type, replace some of the curlies with parentheses, get rid of the labels we added, use infix operator notation, and not worry about it being a valid JSON object, we get: int factorial ( x ) { if ( zerop ( x ) ) 1 else x * factorial ( x - 1 ) } Reformat this a bit, add some C keywords and statement delimiters, and Bob's your uncle. 0 - https://www.goodreads.com/quotes/573737-language-shapes-the-way-we-think-and-determines-what-we https://www.goodreads.com/quotes/573737-language-shapes-the-...
- db48x 1y agoWhorf was an idiot. It’s not worth quoting him.
- AdieuToLogic 1y ago
- pjmlp 1y agoI know Lisp since I read the little lister around 1996, and was an XEmacs user until around 2005. The parenthesis do really disappear, just like the hieroglyphics on C influenced languages, it is a matter of habit. At least it was for me.
- eternityforest 1y agoTo me any kind of deep nesting is an issue. It goes against the idea of reducing the amount of mental context window needed to understand something. Plus, if syntax errors can easily take several minutes to fix, because if the syntax is wrong, auto format doesn't work right, and then you have to read a wall of text to find out where the missing close paren should have been.