Not Lisp again (2009)
funcall.blogspot.com
funcall.blogspot.com
The Abelson from the OP's lecture is the co-author, and the presentation described exactly follows the structure of the book (including the derivative example).
It opened my eyes to a new way of thinking about coding when I first read it (and worked through the exercises) many years ago.
I'm self-taught and it's what made me fully "grasp" programming and the power of abstraction.
Perhaps pragmatism aside, would you?
The same effect worked in reverse as they graduated MIT or worked on projects in more mainstream languages, though. Students would have to unlearn some of the elegant recursive formulations and strong abstraction abilities of Scheme and deal with languages that actually have strong industry adoption. That was a major factor [1] in the replacement of SICP with Python in MIT's intro programming class. Python has abundant library support that more closely mimicks what the programmer will have available and what challenges she'll face in the real world.
[1] https://cemerick.com/2009/03/24/why-mit-now-uses-python-inst...
For someone who is not as well prepared as a typical first-year MIT student, something else might be better, but it’s reasonably accessible in my opinion. I would have loved to have a course like SICP as a high school student.
(Disclaimer: I have not directly evaluated their curriculum/textbook.)
This differs markedly from my own opinion about the proper goals of a ‘computer science’ course at the undergraduate level, which is to teach timeless principles and flexible thinking without bending to fashion (especially fashion which is now a decade out of date), and to prepare students for follow-up computer science courses such as data structures / algorithms, theory of computation, programming languages, or in a more applied direction databases, networking, graphics, operating systems, numerical analysis, machine learning, and so on. A lot of these follow-up courses will be substantially mathematical and will rely heavily on analytical skills developed in in introductory CS course and in mathematics courses (not just on programming skills per se).
But if updated for 2018 the authors’ curriculum would be appropriate as a course titled “introduction to programming” or the like. I agree it sounds like an improvement vs. first courses that start students out on C++ or Java (their main comparison in the linked paper).
YMMV.
That’s only a description of Newton’s method, plus an example on symbolic differentiation (2, respectively 6 pages in a book of over 600 pages, in my copy)
Both are about a tiny corner of differential calculus: differentiating ”expressions that are built up using only the operations of addition and multiplication with two arguments”. It doesn’t involve goniometric functions or integrals.
So, it is is about turning ax² + bx + c into 2ax + b. I don’t know when exactly I learnt that, but it was way before university, when I was somewhere between 13 and 16 years old. It was part of the normal curriculum.
”SICP could be challenging”
I would hope so. If it couldn’t be challenging, how are students supposed to learn from it?
Self-study SICP without any previous programming experience would be exceedingly tough.
I feel like SICP would be a perfect text for introductory CS, if there were a "pre-CS" course in high schools that was essentially equivalent to the current 100-level Discrete Mathematics courses.
Come into programming already knowing set theory, graph theory, computational complexity, etc. and SICP will make perfect sense.
It is described here in an excellent talk about the challenges of curriculum design https://youtu.be/5c0BvOlR5gs note the end of the talk he discusses how Calculus is secretly taught to them as well by keeping track of a video game state over time. Going from Bootstrap->SICP would be possible too.
You don't need to already understand programming to be able to use SICP. SICP doesn't need an intro (or to be modified), it is an intro. What you need to already understand, is math. Specifically, the kinds of math that SICP uses in its examples and problems—which aren't taught in high-school, but totally could be (100-level Discrete Maths has very few prerequisites; you could theoretically learn it in elementary school, though practically not, for mental-development reasons.)
SICP is, essentially, a book to teach programming to "mathematicians who have not necessarily ever heard of a computer." It works wonderfully when used as such. But the less mathematical grounding you have, the harder you'll struggle.
It's exactly like a textbook for one of those "Communications for Business" courses. If you're not a business student, then it'll be a bad communications textbook, because all the examples and problems are business-related.
You are right though I wish there was a course that taught set theory and complexity in HS. Anybody interested in learning both at the same time, there are intro discrete math books that also teach intro programming https://cs.wheaton.edu/~tvandrun/dmfp/ but not in the full CS101 coverage of SICP.
Typical introductory discrete math textbooks list high school precalculus as a prerequisite and recommend an introductory calculus course as very helpful additional background. You would have to teach something significantly different to someone with the background of a typical middle school / early high school student, because they are lacking quite a bit of the expected background concepts, terminology, and notation.
These subjects could certainly be taught to elementary school students, but slowly and with a lot of build-up and practice, over the course of years. The limiting factor is not primarily “mental development” (as in, some inherent change of brain wiring that inevitably happens with age) but rather substantial amounts of practice, prior exposure to related ideas, time spent digesting them, stamina/attention span/confidence at solving technical problems, etc.
That is more or less what this was meant to be:
Honestly, I think it is a superior intro-book for genuine beginners, compared to SICP, and a quick perusal of the two will allow most people to come to a conclusion not only about the level at which they are pitched, but which one is pedagogically superior for them in terms of both structure and writing.
Are they? I don't know if I remember hearing anyone complain about those before.
In TXR Lisp, which is quite CL-like in some places and deliberately so, I used the names succ, ssucc, and sssucc for adding 1, 2, 3, and 4. The inverses are pred, ppred, and pppred.
These are vaguely inspired by Hofstader's S0, SS0, SSS0 ... in Gödel, Escher, Bach as well a by caar, cddr, ... plus the dim memory of operators called succ and pred in Pascal).
SICP is not an introduction to programming as much as it is an introduction to computer science.
I mean, one of the key lectures is about differential calculus. Do you realize that at most universities, CS 101 students are not assumed to have taken calculus. Hell, a large fraction of CS 101 students will NEVER take calculus (non-majors who will instead opt for college algebra).
But that example itself is actually more of a red herring. The point is that the mathematical maturity does provide a lot of extremely useful background thinking/problem-solving skills that most CS 101 students haven't yet developed and will be developing for the first time in CS 101.
I entered as a non-major so did it in c++ first. It only clicked when I went back to sicp. Could just be having done the material twice but I’ve always understood most cs concepts in a functional sense more easily.
I don't think its fair to continue the fiction that introduction to programming 101 at MIT is about pitching to people who have never done programming before any more than it is to pretend that people who get into top medical or law schools haven't had multiple years being coached to get into top medical or law schools.
Humbly, its not a beginner programming book in any reality-connected universe...
It was not and is not necessary to have extensive programming experience to get into a good engineering college in the USA. (Arguably middle schools and high schools should place more emphasis on the subject, but that’s not where we are today.) Many of the best professional programmers I know were first exposed to it in college. Some were first exposed to programming after finishing college, and taught themselves.
Many of the illustrations are based on mathematics and a lot of problems require a decent amount of mathematical maturity to tackle. I think it’s a great book for anyone decently mathematically competent but for people who don’t really know what to write when asked for a proof, I think it’s a lot more difficult.
Apart from the mathematics based examples, a lot of the way things are explained is not very related to actual machines and relies a lot on abstract symbol manipulation, something that is easy not to notice if one is used to mathematics, but is often very difficult if one is not.
I disagree strongly on that assertion. The mathematics in SICP was what made it stand out for me, and what got me into programming. Other books I read before that just presented programming as an opaque thing where you follow instructions and write incantations without any rhyme or reason.
In general terms, the math-phobia that has been prevalent in computer programming for the past 20 years is a new phenomenon (I have not seen it in literature from the 1990s or earlier, or heard it from old-school programmers and CS people) and is at its core deeply anti-intellectual. An example of this dumbing down is the current crop of JavaScript "devs." This runs all the way to the semantics of the language: one of the most idiotic decision made in JavaScript is overloading '+' to concatenate strings. Commutativity? Associativity? Algebra? Get out of here with this math nonsense.
Mathematical approach is a plus to me toi.
So dive on in!
I've only been close to the level but I believe freshman courses of those sorts often involve throwing really smart, cocky kids to a given subject in the most challenging fashion possible. (Remember Caltech used the Apostol text book for calculus, developing everything axiomatically, because, you're smart).
So it is introductory text but it might not a gentle, easy introductory text.
[1] thoroughly explains why
[2,3] based on How To Design Programs [4] is a much better intro; you should read SICP after completing this
[1] https://www2.ccs.neu.edu/racket/pubs/jfp2004-fffk.pdf
[2] https://www.edx.org/course/how-code-simple-data-ubcx-htc1x
[3] https://www.edx.org/course/how-code-complex-data-ubcx-htc2x
One of my favourite pieces of code is a Lisp REPL in Lisp:
(defun repl()
(loop (print (eval (read))))) (-> read eval print loop)(->) inserts each form's value between the function name & the first argument in the next form.
(->>) inserts each form's value as the last argument in the next form.
The latter forms are much more niche and I haven't found need for them yet.
cond-> is great for building maps where some keys might not be needed:
(cond-> {:k1 v1}
v2 (assoc :k2 v2)
...)
some-> and some->> I don't use as often, but when mixing in some java code that could throw an NPE, these will avoid that and just return nil early (they short-circuit when getting a nil value). @(-> ctx
:hypercrud.browser/fiddle
(reactive/cursor [:fiddle/ident])
(->> (reactive/fmap name)))
another (-> [10 11]
(conj 12)
(as-> xs (map - xs [3 2 1]))
(reverse))CLJ:(->)/BS(|.)/RML(->) called Fast Pipe.
CLJ:(->>)/BS(|>)/RML(|>) called Pipe.
plus some nifty placeholders for other positions:
https://reasonml.github.io/docs/en/fast-pipe#pipe-placeholde...
It is totally possible to write custom -> macro that works correctly as used in my example.
The threading macro is great, but the GP one-liner tells me more about lisp.
Still one is the latter and the other, the former. Hence I think the first tells more of the lisp story, with just a few more parens. ;-)
"Why is it called REPL when the code says LPER? Well..."
Is this more readable?
`x ~> square . mean . root`
Where `~>` "sends" a value to a function `x ~> f := f(x)` and `.` is the "backwards" function composition `f . g := \x (g (f x))
I think I prefer the "backwards" notation ("root mean square"), but I can see the appeal of "square mean root"
[0]https://docs.microsoft.com/en-us/dotnet/fsharp/language-refe...
Makes most sense if you think of HTML
<html> <body> <div> </div> </body> </html>
would compose as
compose( html, body, div )
(defmacro -> (&rest args)
(loop for item in (reverse args)
for result = (list item) then (list item result)
finally (return result))) while True:
print(eval(input(">>> "))) ovov@ovov ~> cat repl.py
while True:
print(eval(input(">>> ")))
ovov@ovov ~> python3 repl.py
>>> x = 1
Traceback (most recent call last):
File "repl.py", line 2, in <module>
print(eval(input(">>> ")))
File "<string>", line 1
x = 1
^
SyntaxError: invalid syntaxPython's `eval` also isn't sufficient here. For example, you cannot enter that loop you wrote at the prompt it provides when you run it, even if you write it on one line. You could use `exec`, but then everything would print as `None`.
There's the larger point about forms vs strings and printing readably and such, but this doesn't even provide the same basic functionality that a Python programmer would expect from a REPL.
def repl
loop { puts( eval gets ) }
end
I guess I just prefer Ruby for general legibility. The same type of bracket everywhere makes pairing them mentally an error-prone chore. At least for yours truly.puts and gets are string functions. read and print are code deserializers and serializers.
eval is a function that takes a data structure representing code (as deserialized by read) and computes its value as a data structure, and serializes it.
While you might get the same effect as a user, the mechanics and metacircularity are lost.
Though I guess that wouldn't be valid lisp if you tried to express it literally.
#define DX 0.0001
typedef double (*func)(double);
double NthDeriv(int n, func f, double x) {
if (n == 1) {
return (f(x + DX) - f(x)) / DX;
}
else {
return (NthDeriv(n - 1, f, x + DX) - NthDeriv(n - 1, f, x)) / DX;
}
}
double Cube(double x) { return x * x * x; }
double result = NthDeriv(3, &Cube, 5.0);
As mentioned in previous discussions of this article, the equivalent in Python is pretty elegant: >>> def deriv(f):
... dx = 0.0001
... def fp(x):
... return (f(x + dx) - f(x)) / dx
... return fp
...
>>> cube = lambda x: x**3
>>> deriv(cube)(2.0)
12.000600010022566You are correct that the analytic derivative isn't that hard to do in any language.
Edit: Should have said "many languages" not just "any"
func cube_3rd_deriv = NthDeriv(3, &Cube); /* returns a function */
double result = cube_3rd_derive(5.0); double Cube3rdDeriv(double x) {
return NthDeriv(3, &Cube, x);
}
&Cube3rdDeriv can now be passed around in a higher-order way. #include<stdio.h>
#define DX 0.0001
typedef double (*function)(double);
typedef double (*closure_body)(void*, double);
struct deriv_env { function f; };
struct closure { void* env; closure_body body; };
#define CALL(c, x) ((c)->body((c)->env, (x)))
double deriv_body(void *env, double x) {
function f = (deriv_env *)env->f;
return (f(x + DX) - f(x)) / DX;
}
void deriv(function f, struct closure *out) {
out->env = (void *)f;
out->body = deriv_body;
}
double cube(double x) { return x * x * x; }
int main(void) {
struct closure cube_deriv;
deriv(&cube, &cube_deriv);
printf("%f\n", CALL(&cube_deriv, 2.));
printf("%f\n", CALL(&cube_deriv, 3.));
printf("%f\n", CALL(&cube_deriv, 4.));
return 0;
}But re: the Python code—I would say that, from the perspective of the 1960s, all modern "dynamic" languages that have REPLs (like Python) are Lisps in essential character.
"Lisp", back then, referred less to "a language that uses a lot of parentheses", and more to things like:
• runtime sum-typing using implicit tagged unions;
• parameterization of functions using linked lists (or hash-maps) of paired interned-string "keys" and arbitrary product-typed values, rather than parameterization using bitflags or product-types of optional positional parameters;
• heap allocation and garbage-collection;
• a compiler accessible by the runtime;
• "symbolic linkage" of functions and global variables, such that a named function or variable "slot" can be redefined (even to a new type!) at runtime, and its call-sites will then use the new version.
We only notice the parens as the differentiating feature of Lisps nowadays, because everything else has become widely disseminated. Perl and Python and PHP and Ruby (and even Bash) are fundamentally Lisps, in all of the above ways. Lisp "won."
My point, though, was that while the proponents of Lisp define Lisp one way (by the things only Lisp can do due to e.g. homoiconicity), the opponents of Lisp (like the author was, coming into the course) define Lisp by the set of features that make Lisp "not a Real Programmer†'s programming language"—i.e. the set of things that make them not want to use it.
† http://www.catb.org/jargon/html/R/Real-Programmer.html
My assertion was, from these opponents' perspectives, there are very few languages left for "Real Programmers"; most modern languages have inherited nearly all of those horribly convenient Lisp-isms. Heck—modern CPUs are so good at pointer-chasing, they may as well be Lisp Machines!
On another side of the Smalltalk family tree, languages like Java embraced a kind of static typing and found strong success with it, leading to the promotion of strongly-typed structures over lambdas. This side, ironically, did the most work on JITs, which are meant to compensate for dynamic language features, and languages like Dart are still marrying JIT techniques to strong static type systems today.
And on another side of the Smalltalk family tree, languages like E are fully oriented around objects with rights, so that instead of structures which can be examined, values must have messages sent to them instead. Homoiconicity, lambdas, pattern-matching lists, and other staples of Scheme are nonetheless common in E and Monte.
I think that the Smalltalk family, overall, could be viewed as a response to the barren syntactic landscape of Lisp, but I think that they could also be viewed like any other language: Taking what they like from ancestors, and not taking what they don't like.
That was Lisp back then. Lisp then early on was used in a bunch of symbolic computing domains: theorem provers, computer algebra, natural language processing, planning, rule-based languages, programming language implementation (languages like ML or Scheme were implemented first in Lisp).
In the Lisp sense Python does not have a REPL, since it does not implement READ, EVAl and PRINT over symbolic data, like Lisp does.
def deriv(f): return lambda x: (f(x+dx) - f(x))/dx
or even: deriv = lambda f: lambda x: (f(x+dx) - f(x))/dxMaybe that's the misunderstanding in the original article that made it seem so mindblowing?
Evaluating (f(5.0001) - f(5.0)) / 0.0001 just gives an approximation of the derivative at a certain point in the function (aka the "finite difference method").
https://mitpress.mit.edu/sites/default/files/sicp/full-text/... is the chapter. Turns out it wasn't the "very next" one. But it is in my head.
*Main> derivative f x = (f(x+dx)-f(x))/dx where dx = 0.0001
*Main> derivative (^3) 2
12.0006000100225662017: https://news.ycombinator.com/item?id=14247269 (261 comments)
2013: https://news.ycombinator.com/item?id=5375735 (176 comments)
2009: https://news.ycombinator.com/item?id=504667 (39 comments)
I learned Clojure about a year ago (which was my first introduction to Lisp outside of reading SICP), and it gave me a similar feeling. I felt like Clojure was a "better Java than Java", and now its my go-to JVM language. I think McCarthy was really onto something with Lisp :).
This isn't a particularly unusual approach. Common Lisp, a contemporary Lisp dialect, was designed with efficiency in mind. The approach of "Lisp = slow + inefficient + spending 80% of time on garbage collection" is a myth.
(The ANSI standard was 1994. Still nowhere near 20 years).
Though we've moved from a period of languages as defined by a spec to languages defined by implementation. Such as Perl, PHP, Ruby and Python. So what is "normal" has changed a lot.
#include <stdio.h>
const double delta = 1.0e-6;
double cube(double x) { return x * x * x; }
double deriv(double (*f)(double), double x) { return (f(x+delta) - f(x)) / delta; }
int main()
{
printf("%f", deriv(&cube, 2));
return 0;
}
Am I missing something?but in general a lot of that stuff in the article seems mundane today, you have to imagine seeing it in 1983
How?
If the `deriv` function instead returned a function instead of a number, you could apply the `deriv` function twice.
constexpr double delta = 1.0e-6;
constexpr auto deriv = [] (auto f) {
return [=] (double x) {
return (f(x+delta) - f(x)) / delta;
};
};
constexpr auto f¨ = [] (auto f) { return deriv(deriv(f)); };
double res = f¨([] (double x) { return x*x*x; })(2.0);I find that it follows the functional spec quite closely, thanks to Go functions being first class citizens.
- you can't write a C macro which will do this symbolically. We really just want to be calculating 3 * x * x.
- having cube and deriv, you can't write an expression which combines these two, and is itself a function.
BTW, the & is unnecessary in &cube; a primary expression which names a function evaluates to a pointer to that function.
Here's a much better one that uses a macro to do symbolic differentiation in <150 lines:
https://github.com/aksiazek/symbolic-differentiation
Much harder to do that in C ;-)
Peter Norvig's book "Paradigms of Artificial Intelligence" uses symbolic differentiation as an example in one of the early chapters.
2. The data is also list, and your program is also a list.
3. Algorithm are basically List Manipulation(Stacks, Queues, Trees, Adjacency lists). So its easy to write complex algorithms in Lisp.
4. Tail call recursion. This part amplifies 3. further.
5. Functional programming features.
6. REPL. I mean like a real REPL.
7. Macros.
1 - 7 helps you to express problems and their solutions with code which represents exactly that. The boiler plate and other assisting code is largely non existent.
Another reason is if a thing has been there around for the longest, more people have thought about it. Both quality and quantity of literature of it are higher than others.
Being older doesn’t necessarily mean either quantity or quality of literature will be greater. MUMPS has been around for much longer than Go, for example.
For example, it is a homoiconic language, so any piece of data can be evaluated as if it is code.
The only way to really understand it is probably to learn it :)
Ironically modern Javascript often replicates the bracket pileup, just with "})" instead. Python does away with it by having "nonindented newline" as an invisible semantic character that closes any number of scopes.
You said:
> ... the single representation carries little structural information and the meaning of a symbol is highly dependent on its containing context.
That makes me wonder if the difference is abstract vs. concrete thinking - those who by nature prefer abstract thinking will find Lisp more natural, and those who prefer concrete thinking will find it clumsy and unsettling.
Choosing the "right" programming language is not just finding the right language for the task (though it is that). It's also finding the right language that fits our minds - and our minds are not identical.
I started with BASIC, various Assembler variants, PASCAL, UCSD PASCAL, MODULA 2, ... and then learned Lisp variants and Scheme - and also learned basics of some other languages like ObjectPascal, SAIL, Prolog, Postscript, Smalltalk, ...
That being a problem is pretty much solved by not writing 1000 line top-level expressions with thirty nesting levels.
People who actually write Lisp are engaging their imagination for the program itself. Thus their imagination is too busy to come up with scary reasons how things could go wrong that would spook them out of continuing.
Most of the time a Lisp programmer wants to read WHAT the program does and that in a very descriptive notation.
Any Lisp has a lot of macros. The base Common Lisp language is full of macros. Any defining operator - functions, macros, variables, classes, structures, methods, ... - is already a macro.
For example a structure - a record - is defined like this:
(defstruct ship
(x-position 0.0 :type short-float)
(y-position 0.0 :type short-float)
(x-velocity 0.0 :type short-float)
(y-velocity 0.0 :type short-float)
(mass *default-ship-mass* :type short-float :read-only t))
This is using the macro DEFSTRUCT.It's easy to see that it defines a structure type called SHIP with 5 slots. Each slot has a default value and named options.
A programmer will NEVER need to see what the expansion looks like. The code the macro generates is twenty times larger than the source code. What the programmer actually needs is a documentation of what effects the macro has: defining a type, defining accessors for the slot, defining a type constraint for the slots, making one slot read only, ... This is better read from the documentation of this macro operator, instead of trying to see it from reading low-level operator code implementing them.
Every programmer will be happy to read this macro form - no one wants to see the expanded code, how structures are actually defined in terms of low-level operators.
Thus MACROS increase the readability of programs a lot - independent of the team size. Really no one would want to define a structure type by manually creating all the definitions for it (a type, an allocation function, slot accessors, type predicate, compile time effects, ...).
What they can make more difficult is some maintenance tasks - where bugs appear on a meta-level where programs transform code.
(It's a bizarre, mini language for looping constructions and terrifying animals and small children.)
(loop for i from 10 upto 20
do (print i))
what does it do? Maybe it prints the numbers from 10 upto 20? (loop for element across vector
sum element)
Hmm, what does it do? Maybe it sums all the elements of a vector?Ada:
for E of The_List loop
if Is_Prime(E.P) then
E.Q := E.Q + X;
end if;
end loop;
Lisp: (loop for e in the-list
if (primep (p e))
do (incf (e q) x))
Totally weird and bizarre how it looks similar.Even stranger:
(
loop for e in the-list
if (primep (p e))
do (incf (e q) x)
; end if
; end loop
)The basic idea comes actually from Interlisp and Warren Teitelman‘s ‚Conversational Lisp‘ and its FOR macro.
The main purpose of LOOP is to question ones assumption what is Lispy and what not. ;-)
The paper from 1980 on 'LOOP Iteration Macro' by Burke&Moon does not mention him at all.
Yeah, I might be misremembering.
I still feel current generation of programmers has a learning phobia.
And that's in Scheme, so it doesn't look like someone dropped a chunk of Algol in your coffee.
(I don't know lisp) but sometimes this approach just makes the code really difficult to understand afterwards. Does Lisp have something that makes it easier or better?
[1] Credit where due - I stole that phrase from my friend Michael Pavlinch.
I don't think Lisp is much more difficult to like, on the contrary, out of all the languages I know well (C, C++, Python, Java, Common Lisp) not only do I find CL _by far_ the easiest to write but also that it puts me in a state of flow (= unparalleled mental clarity, focus and productivity) which doesn't easily happen with the others.
Credentials: I spent close to 10 years writing C++ at Google.
I certainly think that Lisp is very different to most popular programming languages today and that difference is immediately obvious. This makes it very easy for people who do not like leaving their comfort zone to dismiss Lisp simply because it "feels" too strange to what they're already familiar with.
- Structural simplicity. Instead of having a huge mess of syntax to keep track of, it has only a very small number of constructs. Simplicity is beautiful.
Criticizing it on the basis of parenthesis would be roughly equivalent to criticizing someone talking about the beauty of mathematics on the basis of the color of the piece of chalk they're using. It's true that it's criticism about aesthetics, but it's not criticism about the aesthetics that the mathematician was talking about.
Once you've understood in what sense people mean that Lisp is beautiful, you can disagree, but this disagreement will not concern parentheses.
As an aside: I have a similarly shallow aesthetic aversion to JS syntax for the same reason. When I encounter a 12-line ragged cascade of curly brackets, square brackets and parentheses, sprinkled with semicolons, I wonder how that can be considered OK.
Aside 2: with Lisp, you edit programs structurally, meaning that their position is essentially managed for you. This is what people refer to when they mean that "the brackets disappear after a while." You're not focusing on them; they're handled by something else.
In my editor, I have the closing brackets faded nearly into the background, which reflects how concerned I am about their existence.
It's considered OK because the extra syntax carries semantic weight, and the semicolons disambiguate intent for the interpreter (because while semicolons are optional in javascript, leaving them out can lead to errors.)
And as someone just beginning to play around with (Arc) Lisp, I still can appreciate both paradigms. Neither is objectively wrong.
Which is a very roundabout way of saying that JavaScript has a very complicated grammar. Is it necessary? No, it isn't. People use and love languages with much simpler grammars, such as FORTH (reverse Polish notation), APL (monadic and dyadic algebraic notation), and Lisp (fully parenthesized Polish notation).
If you think Javascript's grammar is "very complicated" then C++ will probably give you a heart attack.
You're overstating what amounts to an aesthetic argument, which is fine, but not really compelling.
I'm not sure this is a great presumption. Someone can be familiar with lisps, understand the design choices involved, even deeply appretiate the beauty of the resulting language, while still prefering languages with a more flexible syntax
> This is what people refer to when they mean that "the brackets disappear after a while." You're not focusing on them; they're handled by something else.
This is exactly the problem. Lisps surrender the issue of syntax completely to the interpreter/compiler, which makes it easy for a machine to parse, but harder for a human. I personally prefer syntax to be designed for humans to read and write, because its going to be translated to something different to be executed anyway.
Now, the simplicity of the lisp syntax is of course intimately connected to the homoiconicity of the language and the extremely powerful macros, so I do understand the value of it and the tradeoffs involved. (Although I find it slightly ironic how it's venerated, given that the original lisp actually had two syntaxes, M-expressions for the human to manipulate and S-expressions for the machine to manipulate).
I personally prefer to do my "meta-programming" in the type-system and have flexible syntax to express functions in different ways relevant to the uses of the language. That can be Elm with it's simple type system and elegant operators for application (|> and <|) and composition (>> and <<) which indicate direction, or it can be Agda with it's unicode syntax and custom mixfix operators, where you can define a if-then-else as a function as "if_then_else_ : (b : Bool) -> (x : a) -> (y : a) -> a" and call it "if <b> then <x> else <y>". I find that the way these languages use operators, reduces the need for parenthesis and allows you to express what your code is doing, whether it's a pipeline of functions or some imperative-like steps being done in order. The result is a syntax that I find clearer.
I am however, well aware that a lot of people dislike Haskell's use of operators and that these languages are a minority in terms of usage indicating that my preference might not be common, but I don't necessarily presume that it's because they don't understand Haskell.
That is a naive fallacy. What is harder for the machine is harder for the human.
By and large, humans rely on formatting cues, especially indentation, to parse programs. Human eyes can be fooled fooled by bad indentation:
/* C */
if (foo)
bar();
cleanup();
if (outer)
if (inner)
foo();
else
bar();
If you want to compare human versus machine parsing, then you need to write all the code samples on a single line with no breaks (if the programming language allows that). All optional whitespaces that can be written as a single space should so be written. This way, the humans are actually parsing the same tokens as the machine and not relying on cues that aren't part of the language.> I personally prefer syntax to be designed for humans to read and write
There is no such thing in existence. People who design syntax simply use their whims, rather than any cognitive science that brings in any measure of objectivity. Those whims are shaped by what those people have used before.
The concept behind Python is actually the closest to getting it right: it recognizes that people really grok indentation rather than phrase structure, and so it codifies that indentation as the phrase structure.
> Languages are a minority in terms of usage indicating that my preference might not be common
That's another fallacy. The vast majority of all programming and other computing languages that have ever been invented are not used at all, or used by a vast minority. In that vast majority, we can find the full gamut of what has been done with syntax, semantics and everything else. Popularity and lack thereof isn't trivially driven by cosmetics.
No, I'm not claiming an essential difference, merly that they are optimized for different things.
>There is no such thing in existence.
Of course there is, machine code is designed for computers, higher level languges are designed for humans.
>Popularity and lack thereof isn't trivially driven by cosmetics.
I'm not talking about what drives popularity, but what drives cosmetics.
(Machine programs are hard to understand, but in this area we don't have an easy human to machine comparison: machines generally don't understand programs. The advantage of machine language is that it doesn't have to be understood in order to be efficiently executed.)
Yes, you can, but re-read the original comment: the implication was "how can it be beautiful with all those parentheses?" It seems clear that two different aspects of beauty were conflated.
> …what is it about the "beauty" of Lisp that the HN community seems to like so much? … the number of parentheses is just mind boggling. I want to understand why it is so loved.
Also, I would love to hear which language(s) has a more flexible syntax than Lisp, seeing as expressiveness is one of Lisp's strong points.
> Lisps surrender the issue of syntax completely to the interpreter/compiler, which makes it easy for a machine to parse, but harder for a human.
What does "surrender the issue of syntax" mean?
In any case, citation needed for this one. "Easy" and "hard" are very strongly dependent on familiarity. This human finds them easy enough to parse.
> I find that the way these languages use operators, reduces the need for parenthesis and allows you to express what your code is doing, whether it's a pipeline of functions or some imperative-like steps being done in order.
Reducing parentheses is not really a strong selling point for someone familiar with Lisp, because Lisp programmers don't really work with parentheses like in other languages (again, they're managed).
It's not that they're liked for their own sake, but they enable many advantages (such as homoiconicity, structural editing, easy, selective evaluation of expressions at any level of nesting, and so on).
That being said, in Clojure, I often prefer to pipe functions like you mentioned when it seems like the expression would end up unnecessarily nested otherwise:
(-> 8 inc str vector)
;; => ["9"]I'm thinking about ML-like languages such as Elm, Haskell and Agda (to give a spectrum of examples with varying degrees of complexity in terms of language features).
> Lisps surrender the issue of syntax completely
I think I am referring to the same thing as you are when you say that they're not used like other languages, and that they are managed.
>This human finds them easy enough to parse
Easy enough is good, but I think we can do better!
> It's not that they're liked for their own sake, but they enable many advantages.
I think this is what I was trying to get at. :)
FWIW, I like lips a lot, and consider them far better than most mainstream languages.
It doesn't. It just works differently and looks different because Lisp syntax is defined on top of s-expressions. Lisp syntax is provided by built-in syntax for function calls, a bunch of special operators (let, progn, block, catch, quote, function, flet, if, ...) and a zillion macros. Each macro provides syntax - from primitive examples to complex syntax (the LOOP syntax is an example). The syntax is user-extensible by defining macros.
The level of s-expressions is a syntax for data. This syntax can be changed by readtables and readermacros.
Contrast this with most other languages, where there are many things the language does that you can not do. At least, not easily.
There aren't actually more parens in a Lisp program than a program written in a C-like syntax (which is really an algol-like syntax). They just stand out more for two reasons:
1. There is less punctuation in general, so the parens are more obvious. Instead of f(x, y, z) you write (f x y z). Without the commas, the parens stand out because that's all that is left.
2. There is only one kind of parens in Lisp whereas C-like languages use at least three: (), [], and {}, so that makes any particular kind of paren less prominent.
4. The lisp punctuation style closes all levels on a single line, where most C code guidelines close one level per line. This leads to a "thick" chunk of close parens that students of lisp find harder to read at first.
Well put, Madam/Sir.
No, instead you get something like );}]);) except that that's usually split up over several lines.
BTW, it's pretty easy to tweak Lisp's syntax so that your parens don't get so deeply nested. See
https://github.com/rongarret/tweetnacl/blob/master/ratchet.l...
for an example.
1. Other languages have operators which can be written without brackets, x + y vs (+ x y).
2. Precedence rules allow ex. polynomial expressions to be written without brackets, 2 * x + y vs (+ (* 2 x) y).
3. Algol-like let-bindings or monadic-do-style variable-bindings that inject bindings into the containing block (as opposed to Lisp-style let-blocks where the new scope typically corresponds to a new block) use fewer brackets and keep nesting depth smaller.
# 2 sets of brackets
fun foo(x, y, z) {
let w = x + y;
let u = w + 2*z;
let v = w - 2*z;
u * v
}
# 13 sets of brackets
(fun foo (x y z)
(let ((w (+ x y))
(u (+ w (* 2 z)))
(v (- w (* 2 z))))
(* u v)))http://www.flownet.com/gat/lisp/parcil.lisp
See:
http://www.flownet.com/gat/lisp/djbec.lisp
for some example code that uses this parser. Scroll down about half way and take a look at xpt-add and xpt-double.
((+ ((* 2 x)) y))
"I have this sublanguage..." is not a very interesting participant in the evidence that Lisp doesn't have lots of parens.I'm still fond of Lisp, but languages like Python, D, Nim (and increasingly, Rust) offer a lot of the Lisp affordances that I really valued -- in particular, easy compile-time metaprogramming. The biggest missing bits are Lisp's deeply integrated REPL-driven programming, and the use of program images -- which have many drawbacks but also some benefits.
IMO, everyone should write and maintain a medium-sized program in a modern Lisp (SBCL, CCL) at least once, just to get an appreciation for how different the programming / debugging experience is. So many well-integrated tools at your disposal, even at runtime.
Lisp (or Scheme, in this case) is a very simple language that gets essentially everything right.
I have to wonder if you actually know all that much about Scheme. Undelimited continuations, for example, are pretty much strictly broken. I like Scheme, but to claim it's flawless is kinda ridiculous.
As far as syntax goes, the proof is in the pudding. All languages have people gripe about some part of their syntax or another, but it's clear from experience that "OMG parentheses" is exceptionally prevalent. Hand-waving it away as something that people just don't get because of lack of prior exposure is not really sound - somehow other languages don't get similar complaints (at least, not as universally, and not to the same magnitude) with first-time users. Besides, why is there a lack of exposure? Why, because everything else is different. But why is it different? Isn't the obvious conclusion that Algol syntax family is vastly more prevalent for the simple reason that people prefer it, and simple homoiconicity is not sufficiently enticing?
All arguments in favor of Lisp syntax feel like they ultimately boil down to "you're holding it wrong". And that may well be so - but if so many people are finding it so awkward to hold, isn't that prima facie evidence of ergonomic deficiency? I don't claim to understand why Algol-style is easier. Maybe the way our visual processing works is just better with more varied punctuation? That's something for psychologists and brain scientists and maybe linguists to figure out. But in the meantime, we could at least acknowledge the way things are. I really like Lisp as a collection of ideas (not just the usual ones like HOF and macros, but also stuff like e.g. symbol-based namespacing, or the sheer flexibility of CLOS). But lispers have to ask themselves why, instead of Lisp seeing wider adoption, other languages - that came literally decades later, so "upstart" would be a very polite way to describe them in this context! - become vastly more successful than Lisp by appropriating its cherry-picked features.
That is just confusing cause and effect.
> All arguments in favor of Lisp syntax feel like they ultimately boil down to "you're holding it wrong". And that may well be so - but if so many people are finding it so awkward to hold, isn't that prima facie evidence of ergonomic deficiency?
You are confusing cause and effect again, this time by implying that there is something fixed about the way people learn languages (Chomsky's "language organ"). Learning is not like the shape of your hand.
Once you learn structured editing, Lisp code can be written and changed in much fewer keystrokes than code expressed in a more complicated grammar, which is actually ergonomic.
And what is the evidence to that claim?
I'll grant you that I haven't given any evidence for my claim, either. But, again, the onus is on those claiming that syntax doesn't matter to prove so, against overwhelming practical evidence (showcased by user count) that it does.
So many computing languages have come and gone over the years; the number that have been created vastly outnumber those that have ever been popular, let alone that are now popular.
Lisp is amazingly vibrant as a family. People are still excited about it and there is development work going on. That's amazing for something with such old roots.
I added two instructions to the virtual machine this morning, and used them in the compiler of a Lisp dialect to eke out a little performance gain. Wo hoo!
The haters can all go stuff it.
Of course they are not. If you put on a shoe, and it's uncomfortable, you just go get a better shoe that is. This does not negate the validity of the question: why was the first shoe uncomfortable?
You seem to think that popularity should be the prime consideration when it comes to programming language design. This is what gave us PHP and Javascript. I dare say that the people that use Lisp today (and there are plenty of those) do so because nothing else will be as good to them. They love the language. I've known people who moved jobs and got less money in order to work with Lisp. What does that say about the language?
OTOH distinctions like expressions vs statements don't apply -- my years as a lisp developer were the most productive of my life.
At one point I wrote my own Pascal interpreter for the C64. In BASIC, because I wasn't good enough to do it in assembly. It was a little slow.
I desperately wish someone had introduced me to lisp. The entire course of my life would have been different. The most frustrating thing is that I probably could have written a naive lisp interpreter in assembly.
However, I very well remember reading the Compilerbau book by N. Wirth and the awe I felt. It’s just a few pages but it was a revelation.
They are all available on the ETH website and his homepage (in various revisions). Basically everything you need to know about imperative programming on CS bachelor level for free.
I now focus primarily on Rust, which has given me a lot of the same feelings as when I first encountered Lisp. It's truly a language ahead of its time, and I hope it will continue to grow beyond what even Lisp was able to accomplish.
https://gist.github.com/divs1210/4ca74577711eb996a89a36d86a3...
Well, not that simple... as the author hasn't taken the potential problem of free variable capture into account.
They seem available in other functional languages.
I was under the impression LISP was unique due to its macros.
Joking aside, I think lisp is nearly fundamental in the same way that c is.
i mean, if i have a lisp with static types vs not static types, why is the lisp part important at all? and you just have two different languages one with static types and one without?
seems very unimportant? i dunno could someone enlighten me
Also, up to that point the author has only programmed in a couple of different assembly languages, plus Basic. Coming at it from that perspective, there's plenty of novelty to be found in Lisp family languages.
The author seems to be making the case that, contrary to his original skepticism, Lisp is indeed some magical higher-order language, but I honestly don't see it. Lisp's ratio of philosophizing to noteworthy projects seems to tend toward infinity.
Like others writing here and like the author of the post I, too, had my imagination fired by SICP and the culture it fits in. When I pick up a Learn to Program book and the examples are managing a used car dealer's inventory, I can only say "Thanks" to Abelson and Sussman. They turned me on to something exciting.
but in 1983 this would have seemed magic.;
Just optimizing tail calls as written in the programmer's code (which is what is normally meant with "TCO") don't need such rewriting, and would be trivial to implement via a jump if the back-end supports that, except C also needs to take into consideration structures on the stack, and perhaps other details, hence apparently gcc's handling isn't straight-forward even for plain TCO and fails to be applied in all applicable cases (or at least that's what happened ~4 years ago).
(deriv (deriv f))
or if you wanted to take the nth derivative
(define (nth-derivative f n)
(if (= n 0)
f
(deriv (nth-derivative f (- n 1))))
You could do it with some data structures, which you'd then have to interpret, but not as nicely or easily. #define DX 0.0001
typedef double (*func)(double);
double NthDeriv(int n, func f, double x) {
if (n == 1) {
return (f(x + DX) - f(x)) / DX;
}
else {
return (NthDeriv(n - 1, f, x + DX) - NthDeriv(n - 1, f, x)) / DX;
}
}
double Cube(double x) { return x * x * x; }
double result = NthDeriv(3, &Cube, 5.0);