Hacker News new | ask | show | jobs
by dragonquest 3526 days ago
Learning Lisp, more specifically Scheme. I became curious about it after reading ESR's "How to Become a Hacker". When homoiconicity and other Lisp goodies gradually permeated my brain, the computing world seemed different.

I saw every config file differently, trying to see how the lines between config and code blurred. Method abstractions and bottom up design became much easier. I appreciated all those prefix-evaluation assignments in college. I found a new respect for languages with good REPL's. And finally, it opened my eyes to computing history, where I discovered newer is not always better. The elders of old really got many things right.

2 comments

Scheme changed everything for me.

I feel sad when I hit special forms in languages, and can't just a macro to change the language to suit the way I want to do it.

It made every other language I used feel overly verbose. Sure, Scheme is a bit verbose, but with two or three macros, and a handful of recursive functions, I write about 1/3 of the code I would in another language.

Having readers, and parsers exposed makes it so easy to fit the language to the task, rather than bludgeoning the language with a hammer, or rephrasing the question till it almost fits.

Random example:

You need to allow a user to run half a dozen functions, with a syntax.

Normal approach, (after you've argued you shouldn't do this), is to write a parser and evaluator, and hope you didn't expose a security hole like SQL injection by accident.

Scheme approach: Generate an empty environment, attach only the necessary functions. Parse and handball to the environment.

Eval doesn't have to be evil, especially when the language provides the tools you need to sandbox users.

It's still evil - what if the input program doesn't halt, or allocates too much memory?
You design the functional building blocks. So a non-halting program would likely use a single stack, and never grow. But more than that, you can use continuations to abort the moment the calculation is too large.
Any examples of using continuations to abort when the calculation is too large? Still grappling with continuations, familiar with coroutines/csp/fibers much more for concurrency, but trying to get call/cc fully under the belt.
I'd probably nest a named let inside a continuation. Raise an exception when an iterator goes too long. Something like that.

I tend to avoid continuations in production code, because how it reacts very much depends on what you're accomplishing. Most exception handling frameworks use continuations, which you can accidentally turn into infinite loops if you don't abort the loop properly.

> You need to allow a user to run half a dozen functions, with a syntax.

You could do this with Javascript 'eval' too and I still wouldn't recommend it. Usually when this sort of requirement comes up the answer is actually visual programming, where the user just has to join blocks up and get's some sort of immediate real time feedback [1]. It's the only time VP is a good idea as the problem is in an extremely extremely constrained environment. Putting Lisp in it's place is really just an all round awful solution. Look at how the AutoCAD Lisp experience turned out [2].

[1] http://acegikmo.com/shaderforge/

[2] https://www.youtube.com/watch?v=LM1RMgIdgR8

JS doesn't have first-class environments, so you can't have the same safety guarantees. Added with its weak typing... You could instruct JS to calculate anything.

Visual Programming might possibly be useful for end users, but in most areas, there are better approaches.

Serving limited environments to users is useful in at least these situations:

* nREPL * Database APIs (ala firebase) * Mathematical APIs (ala Mathematica)

Some of these I've needed in production, such as more precise calculations for statistics. (FloNum integrations into pre-existing applications).

My main point was, LISP is unconstrained, but still gives safety to the programmer.

A simple example of a first class environment:

    (eval (read (current-input-port)) 
    (let
      ((add (lambda args (apply + args))) 
        (null-environment))) 
The only function exposed is called add. There isn't a way to break that. It makes embedding Scheme into applications such as GIMP or games much easier.

(Just a side note, antimony [0] seems to be gaining popularity in its niche)

[0] https://github.com/mkeeter/antimony

A Paul Graham article lead me to Common Lisp and Scheme, SICP, On Lisp which lead to Clojure which lead to being more interested in functional programming, which lead to functional reactive programming and Scala. Quite a long ride, but I'm so happy I'm not writing OOP C++ which was a very dull and frustrating part of my career.