Live data from Hacker News

Understanding the Power of Lisp (2020)

joshbradley.me

101–110 of 140 posts

Re: Understanding the Power of Lisp (2020)

#101
post #22

Earlier quoted context omitted.

here's how to leverage the pretty printer to transpile lisp into pascal https://merl.com/publications/docs/TR93-17.pdf

That’s hilarious to me because I remember someone at UT wrote a pascal compiler in Lisp so TeX would run on the lispm.

Symbolics provided Pascal, Prolog, Fortran 77, C and I believe Ada (but I never seen that one) implementations that used ZetaLisp as intermediate language.

The Pascal one was used to build TeX, indeed.

Re: Understanding the Power of Lisp (2020)

#102
post #63
post #42

Earlier quoted context omitted.

> Still, the basic eval command looks remarkably similar in lisp vs TCL. The primary difference is whether arguments are evaluated before (Scheme/Lisp) or after (Tcl) being sent to the function/command. The deferred evaluation is what allows you to do things that you need macros for in other languages. (ie. you need deferred evaluation or macros to implement short-circuit operators, for example) However, early Lisps…

If I am reading this right, TCL's approach is neither FEXPRs nor Lisp's macros -- instead, it is a very concise "quote" command and "uplevel" command which is something I have not seen widely used in any other language. In the lisp terms, imagine a language with no macros and with immediate evaluation ("applicatives" in the Kernel-speak). And no special forms either, except for syntax-level quote command. This means…

In my opinion, there are two big things that Kernel brings to the table:

1) Arguments are not automatically evaluated before being passed to the underlying "operative" in Kernel-speak. This is important because it obviates the need for the macro machinery. This is fairly similar to Tcl.

2) Environments are first-class in Kernel. This is important because you can now constrain the scope explicitly. Do I want to evaluate this in the defining context (lexical scope)? Do I want to evaluate this in the calling context (dynamic scope)? Do I want to evaluate this is global or initial scope? All of these things are possible because environments are a thing that can be passed around.

The downside is that this level of mutability seems to constrain the ability to pre-compile things.

One of the "problems" that I would consider Tcl to have is that certain "atoms" don't self-evaluate. There's really no good reason for why you have to write:

% if 1 {expr 2} else {expr 3}

instead of

% if 1 2 else 3

And no good reason why you can't write:

% + 1 2

3

instead of

% expr 1 + 2

3

This seems like going gratuitously out of the way to avoid "looking like Lisp" which was in its down phase by 1990.

Re: Understanding the Power of Lisp (2020)

#103
post #59
post #45

Earlier quoted context omitted.

I know a startup that hired a Marxist-collective group of programmers. I was told this story years ago, and they were acquired, so I’ll just name the firm - “White Ops”. This story is so absurd that I’m naming the firm in hopes someone can verify the accuracy of this, although I trust the person that told me the tale. The programmers were based in Canada and only wrote in Haskell. The CTO of White Ops had apparently…

You can do that in almost any language, though. There was a furry who worked as a web developer for a small firm. He had built their back-end runtime in MUCK scripting language. (A MUCK is like a MUD, but without the statistics and stuff, so it's much less a role-playing game than it is simply role- playing .) Eventually he was fired, probably because he spent more time pretending to be a highly sexualized female fox…

I can't even. Did they use the one that's basically Forth with a string stack (No, not Postscript, Forth with a string stack) or the one that's basically m4 (the higher level of the two sendmail scripting languages) implemented in Forth with a string stack?

Re: Understanding the Power of Lisp (2020)

#104
post #34

I have the same response to this as I do to every other lisp article. It’s cool, but how have you personally leveraged this advantage that everyone talks about? What big lisp project has the author contributed to, such that they have enough data to sing it’s praises?

I'm not a hardcore LISPer anymore, but I worked on a ~600K LISP system in my first job out of college for 4 years. I'm many years away from it, but I still remember it fondly.

We were working on a DoD project to feed relatively large amounts of logistics data in and out of a bunch of simulation models written for different contracts for different branches of the military. The inputs/outputs were all relatively large/complex files, with different formats and different units, and some values had to be repopulated in the output-files before being fed into the next simulation, and the input/output formats of many of these things were often being changed. The whole environment we lived in was more dynamic than you'd ever really want, yet I remember it feeling really easy to describe what I wanted to do in code; more than any language I've worked in since, stuff tended to Just Work the first time.

Some of the cooler things one could do in LISP that were difficult/impossible to do in other languages:

- Running in an interpreter from your editor (emacs, of course) allowed you to write editor code that could read/rearrange/fix your application code in the running image making the tweak/reload/test cycle trivially fast, even in really complicated systems (Python comes close, but not quite there).

- If it made sense, you could write lisp code in your editor that helped you write/manage the LISP code for your project, create patches automatically, etc.

- Writing a refactoring script in your editor was often better than trying to do it by hand. Decades later, I have yet to see an IDE or a refactoring tool that works as cleanly.

- The LISP macro system made it "easy"[1] to avoid writing boilerplate code. It was often more practical to write a boilerplate-generator, rather than thousands (on 10k) lines of boilerplate.

- Since classes/objects could be redefined on the fly almost arbitrarily, it was possible to redefine object data members as static class-members, and subclasses with overridden member-data, depending on the contents of your dataset AFTER loading it (at the time, I think this had a RAM savings of ~30%?)

- CLOS is the cleanest, most flexible object-oriented language I've ever seen, with a sensible multiple-inheritance system and all sorts of tools (like "before", "after", and "around" methods) you could use if you had a good reason to.

- The metaobject stuff in LISP is deep, dark magic; but sometimes you actually need deep, dark magic. Not often, but sometimes.

In the end, I think LISP is still a great choice for really complicated, dynamic problems (the kind that take years and millions of lines of code to solve). If you find yourself working on the sort of problem that makes you feel a need to build a domain-specific language to solve it, that's the sort of problem that LISP is REALLY good for. While I'm not a manufacturing engineer, LISP feels a bit like the programming language equivalent of a CNC mill/lathe that you can use to build out a factory that might require building bigger/better CNC mill/lathes.

[1] "easy" in that the non-boilerplate code was easy to read and understand its intent, but often it was much harder to debug, as macros often called macros which called macros. Debugging code that writes code is a whole different level of brain-hurt, but sometimes it's still better than the alternative.

Re: Understanding the Power of Lisp (2020)

#105
post #40
post #37

Earlier quoted context omitted.

Here's a simple example you can't write in most languages. (first-working (get-it-from-the-cache) (get-it-from-the-database) (get-it-from-an-external-api) (compute-it-the-slow-way) default-value) Note each of those functions could throw an exception. You want to ignore it (or you could log it) and move on to the next.

Maybe I am missing something. Here's how I might write something equivalent in Python: for f in GetFromCache, GetFromDatabase, GetFromAPI, Compute: try: return f() except Exception as ex: log(ex) return default_value (You'd probably define a custom exception class for your application that represents the kinds of errors you can tolerate, so you don't end up swallowing programming errors, but you get the idea.)

I'm the one who missed something: each of my examples called its functions with no arguments. Your approach would, of course work when the functions are called with no arguments or the same arguments.

    (first-working
     (get-it-from-the-cache (or local-cache default-cache))
     (get-it-from-the-database current-database-connection)
     (get-it-from-an-external-api (ask-user-for-api-credentials))
     (compute-it-the-slow-way foo bar baz)
     default-value)
Each expression can be arbitrary code where to do something similar in Python you'd need to compute a thunk in advance.

There's also an advantage in clarity because an abstraction can be defined. Once you know what first-working is for, the purpose of this block of code is clear as soon as you read the first token. Using a pattern instead of an abstraction, it's necessary to read the whole loop to make sure it's really the pattern you think it is.

Re: Understanding the Power of Lisp (2020)

#106
post #40

Earlier quoted context omitted.

Maybe I am missing something. Here's how I might write something equivalent in Python: for f in GetFromCache, GetFromDatabase, GetFromAPI, Compute: try: return f() except Exception as ex: log(ex) return default_value (You'd probably define a custom exception class for your application that represents the kinds of errors you can tolerate, so you don't end up swallowing programming errors, but you get the idea.)

In ruby your lower level API should support methods with ! syntax that throw and without it that return nil instead: def get_from_cache get_from_cache! rescue NoRecord nil end Then you just do this to not only do it all but only do it once lazily on access: def record @record ||= get_from_cache || get_from_database || get_from_api || compute || default_value end Be slightly better if ruby supported a proper null coal…

> In ruby your lower level API should support...

Assume, for purposes of the exercise that someone else wrote some of the methods you're calling and they do not behave this way. You could write your own wrappers, but that's a lot more code to write and maintain than a simple control structure that doesn't care what's inside it.

Re: Understanding the Power of Lisp (2020)

#107
post #59

Earlier quoted context omitted.

You can do that in almost any language, though. There was a furry who worked as a web developer for a small firm. He had built their back-end runtime in MUCK scripting language. (A MUCK is like a MUD, but without the statistics and stuff, so it's much less a role-playing game than it is simply role- playing .) Eventually he was fired, probably because he spent more time pretending to be a highly sexualized female fox…

I can't even. Did they use the one that's basically Forth with a string stack (No, not Postscript, Forth with a string stack) or the one that's basically m4 (the higher level of the two sendmail scripting languages) implemented in Forth with a string stack?

I think they used MPI (the m4-like one) because it is much easier to retrofit as a sort of templating language for use in web pages back in the 90s when that was a thing, as opposed to the modern practice of maintaining an entire parallel-universe DOM which is then transformed into the real DOM. MUF (the Forth-like one) was a bit too low-level for the application, though I do seem to recall a story this same furry wrote in which a fantasy CPU is mentioned that runs MUF natively, like a Lisp machine but for furry roleplay environments. I won't get into the uses to which it was put.

Re: Understanding the Power of Lisp (2020)

#108

Earlier quoted context omitted.

Self-modifying code isn't a bad idea. Like everything else, it has it's place, just not typically on modern computers due to breaking optimizations. What you describe is actually used in a lot of modern software, usually self-modifying code refers to code that modifies itself while running, typically in a loop, not just generating fresh code :)

In my opinion, compile time codegen is way better than runtime codegen. Unless you are exploring genetic algorithms, runtime codegen is slow, brittle, and undebugable. Compile time codegen, however, can produce ridiculously fast code (see FFTW), and is much easier to debug because you can look at the generated code.

> brittle and undebuggable

That's why I stopped doing it, but I totally get that in low level programming that it comes in very handy in some circumstances.

Re: Understanding the Power of Lisp (2020)

#109
post #76

Earlier quoted context omitted.

So Clojure programs are data structures, and you can pass a structure to a macro to rewrite it into something the compiler can evaluate. In this way you can extend the language. You can build your own DSL that solves your particular problem in an organic way that grows as you go along. If I understand that correctly I don't get it, because I would still have to write the macro, which in C# I would write as a function…

A canonical example of macro capabilities that methods/functions cannot recreate is a short-circuiting conditional. Clojure's only built-in conditional operator is 'if'. Despite this, we have nice short-circuiting or, and, when, unless, and other conditional operations in Clojure, defined as macros. Clojure (and C# and most languages) eagerly evaluate their function arguments. You cannot write a function that short c…

Thank you for the examples. I'm really curious to get to the bottom of this. My inspiration has been Erik Meijer, specifically his article "Confessions of a Used Programming Language Salesman. Getting the Masses Hooked on Haskell" [1]. If I understand you correctly, you are implementing lazy evaluation in your when macro, something you call short-circuiting.

By dropping laziness in strict functional languages such as Scheme, SML, OCaml, Scala, and F#, the purity and semantic beauty of true functional programming is lost. When programming in Haskell, laziness is one of those things that you rely on all the time without noticing it; it is something you only realize once you miss it.

As for async/await, Erik led the effort to put it in C# and then Dart. I'm assuming he wanted to bring Haskell Continuation Monads [2] to the popular languages.

I'm building a hobby website, so I got into Blazor because I like C# and was interested in doing it client side. I gave up on it, not because of C# or bad implementation (it's actually great) but because Microsoft has a noisy and bureaucratic way of configuring code that drives me nuts. I went back to Dart because it is amazingly productive on the client side, but now this video [3] shared in this thread makes me think I should give ClojureScript a try.

[1] http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72....

[2] https://www.haskellforall.com/2012/12/the-continuation-monad...

[3] https://vimeo.com/230220635

Re: Understanding the Power of Lisp (2020)

#110
post #100

Earlier quoted context omitted.

So Clojure programs are data structures, and you can pass a structure to a macro to rewrite it into something the compiler can evaluate. In this way you can extend the language. You can build your own DSL that solves your particular problem in an organic way that grows as you go along. If I understand that correctly I don't get it, because I would still have to write the macro, which in C# I would write as a function…

Consider that macros generally don't expand syntax - because there is very little syntax anyway, and what you have is a tree of data and function calls. A Macro just lets you write a function that will be executed by compiler to manipulate such tree, meaning that you can have equivalent of adding syntax like "with/using" from some languages by writing a function that takes as arguments the object and a block of code,…

I generally never used macros when I wrote C because the C macro language is lacking, there are a lot of pitfalls and saving function call overhead is usually not that important.

I think I'll try to get a new understanding of Lisp macros by trying to come up with a language feature in C# or Dart that I really want but can't implement without a lot of non-intuitive difficulty, but could easily in a Lisp style macro that the compiler could use to build in a new feature at compile time.

Post reply on HN