But readability has a lot to do with what you are used to.
The only exception might be FORTH. A very well written FORTH implementation (and I mean very well written) probably would be fairly readable to anyone — at least at the higher levels of abstraction.
https://en.wikipedia.org/wiki/Charles_H._Moore?wprov=sfti1#E...
"In 1968, while employed at the United States National Radio Astronomy Observatory (NRAO), Moore invented the initial version of the Forth language to help control radio telescopes."
Joking aside, FORTH’s reliance on the stack as virtually its only data structure – along with its insistence on applying it to everything and everyone – is simply too impractical outside a few niche areas, such as low-level hardware programming or document rendering (hello, PostScript!). I have no doubt a JSON parser in FORTH will emerge as part of Advent of Code 2038, but I can’t imagine it will inspire hesitating potential converts to embrace the language with open arms.
It's a lot prettier than :=
b _ 5
and see b ← 5
and copy / paste here and see b := 5
https://cuis.st/God, Lisp...the core language isn't exactly that interesting in this day-and-age. Dynamic typing, garbage collection, anonymous functions, this has been the bread-and-butter of Python, JS, Ruby, etc. developers for like 20 years now. It's got some really powerful features like conditions and CLOS. But then the bulk of the language and library built on top is such a mess: it's missing so much basic functionality, but also has so many niche functions that can be configured every which way with niche keyword arguments, and they all turned out to be evolutionary dead-ends and much worse than what the rest of the world settled on (actually CLOS probably falls into this category too). I think it's this, more than anything, that makes programming in Lisp feel like an act of trickery rather than clear thinking.
But I'll also say that I've been hobby programming in Lisp a bit recently and, despite that, I've been finding it immensely pleasurable, the first time I've really enjoyed working with a computer in years. The highly interactive, image-based workflow is just so much smoother than my day job, where I'm constantly jumping between VSCode and Chrome and a console and manually rebuilding and sitting around waiting...
Macros may be a double-edged sword - they encourage monstrosities like LOOP, rather than building more powerful/regular/comprehensible language features like iterators or comprehensions. BUT when paired with that interactive development experience, it really feels like you're in a dialogue with the computer, building out a little computational universe.
Common Lisp genuinely expanded my thinking about programming, so I find this article's poetry analogy very apt. But part of this growth was clarifying my own preferences about programming, and some of CL's greatest strengths - extremely clever runtime meta-programming, CLOS, etc - are not actually things I want to work with in a code base at scale.
I also think the UX for CL, outside the commercial Lisps, is pretty grim. CL would greatly benefit from a rustup-cargo-like system with good dep management and sane beginner defaults, like wrapping the SBCL REPL in rlwrap. Haskell is more beginner friendly than CL right now, and that's quite the indictment.
In any case, I think every developer should have a few languages that they really enjoy using.
CL still got symbols, the reader (and its macros), gradual typing and user available runtime compilation (compile and compile-file).
I find the core language itself near perfect (mostly the historic stuff like threads/atomics/unicode missing, the whole divide between normal CL and CLOS and lack of recursive and parametric typing") but the standard library quite baroque and lacking; still infinitely more serviceable than C's, though.
TypeScript has all of these, too.
(define (time-to-move from-pos to-pos)
;; Calculates the total time to move between two positions,
;; assuming rotation and ascension can occur simultaneously.
(max (time-to-rotate (position-azimuth from-pos)
(position-azimuth to-pos))
(time-to-ascend (position-elevation from-pos)
(position-elevation to-pos))))
It was a very long time since I first learned programming.
It was QBasic first I think.
It was a very slow process that I had mostly forgotten.
It took me a very long time to grasp what's now very basic control flow.
But I was like 7 or 10.
I remember feeling like "this is pure magic!" so many times so very early on.
Part of what I want to do is rekindle that like pico8 did for me. Somehow.
I fixed that in TXR Lisp:
(defun time-to-move (from-pos to-pos)
(max (time-to-rotate from-pos.azimuth to-pos.azimuth)
(time-to-ascend from-pos.elevation to-pos.elevation)))
The remaining issues are self-inflicted verbose naming. I would make it ;; time to move
(defun ttm (p0 p1)
(max (ttr p0.az p1.az)
(tta p0.el p1.el)))
If the entire program fits on a few pages, you can remember a few abbreviations.The dot notation is a syntactic sugar for certain S-exps:
a.b.(c x y).d -> (qref a b (c x y) d)
.a.b.c -> (uref a b c)
there exist macros by these names which take care of the rest.Starting in TXR 300, there can be whitespace after the dot. So while you cannot write in in the popular way:
obj
.(meth arg)
.(next-meth 42)
you can do it like this: obj.
(meth arg).
(next-meth 42)
this has a lot do with Lisp already having a consing dot.Lisp doesn't mean being against all syntactic sugars; e.g. we mostly write 'X rather than (quote 'X).
But note that even if we don't have this notation, how the author has tripped over himself to create verbosity. After two nestings of with-accessors he ends up with:
(time-to-rotate from-az to-az) (time-to-ascend from-el to-el)
Here from-az is not a whole lot shorter than (az from)! If he used shorter names like el and az, he would have: (defun time-to-move (from to)
(max (time-to-rotate (az p0) (az p1))
(time-to-ascend (el p0) (el l1))))
Don't make names for yourself that you then have to shorten with clumsy macros that make the function longer overall.Another thing is, why does time-to-move take two objects? But time-to-rotate and time-to-ascend work with destructured azimuths and elevations?
(defun time-to-move (from to)
(max (time-to-rotate from to)
(time-to-ascend from to)))
Time to rote and time to ascend could be inseparable. If the device is mounted on an incline, the time to ascend may depend on the azimuth. It may be better to rotate it first than elevate or vice versa, or execute some optimal combined motion.The moment of inertia of the thing may depend on its elevation; it may be easier to rotate in a high elevation. So just time-to-rotate alone needs the whole object.
(destEl-sourceEl)⌈destAz-sourceAz
Honestly, though, we don't really know what any of the reference implementations do. The core structure structure is just (max (f x y) (g z w)), and we have to simply guess at the meaning of f and g by their names. The above APL takes a similar liberty, but instead of a fiat declaring, we just a fiat declare that our data is sufficiently normalized.Here's my take on performScan, too:
Log⍪←onSourceTime+slewTime
Why would you make an assumption like this and try to compare code? If the sample code were simply subtraction then OP would've written subtraction. What use is demonstrating APL code that solves a different problem? If you're gonna do that, instead of removing part of the problem you might as well remove most of the problem and just say the solution is:
⌈/dest-source
And then write some sentence about saying how that's valid because you declare they are normalized even further.
> Here's my take on performScan, too: > Log⍪←onSourceTime+slewTime
How is this valid if you don't actually do any work to calculate onSourceTime or slewTime? You took the last line of performScan and implemented it. You do need to also implement the other lines in the function.
Maybe I'm just completely missing something here.
2d is bigger than 1d, but sometimes it is smaller too:
⌈/dest-source
I only understand the Haskell code as well as I do because the comment describes what it does (much more clearly than the docstring on the lisp function). Others have already posted about how unnecessarily verbose the lisp function is, so I won't rehash that part here.
Think about it. What is the smallest most primitive computation that is modular?
If you have a method that mutates things, then that method is tied to the thing it mutates and everything else that mutates it. You can't move a setter away from the entity it mutates without moving the entity with it. It's not modular.
What about an IO function? Well for an IO function to work it must be tied to the IO. Is it a SQL database? A chat client? If you want to move the IO function you have to move the IO it is intrinsically tied with.
So what is the fundamental unit of computation? The pure function. A function that takes an input and returns an output deterministically. You can move that thing anywhere and it is also the smallest computation possible in a computer.
The reason why haskell is so modular is it forces you to segregate away IO and purity. The type system automatically forces you to make the code using the most modular primitives possible. It's almost like how rust forces you to code in a way that prevents certain errors.
Your code in haskell will be made entirely of "pure" functions with things touching IO being segregated away via the IO monad. There is no greater form of modularity and decoupling.
In fact any other computing primitive you use in your program will inevitably be LESS modular then a pure function. That's why when you're programming with OOP or imperative or any other style of programming you'll always hit more points where modularity is broken then you would say haskell. That is not to say perfect modularity is the end goal, but if it was the path there is functional programming.
Smalltalk, Haskell, and Lisp
...walk into a bar.
Smalltalk: “Bartender, please send an instance of Martini to me - selector withOlive: set to true.”
Haskell: “I’ll take beer >>= chill, but only if it’s defined for all inputs and returns IO ().”
Lisp: “(drink ’(ale (temperature cold))) .”
The bartender mutters, “Great ... an object, a functor, and a list.”
Then a janitor walks by sweeping the closures that they left.
"cold temperature ale drink"
to which the bartender replies "go you here". The janitor smiles as no sweeping required on a clean stack.
to which the bartender replies "ok"
throwaway81523•11h ago