In Polish auto repair, if nowhere else:
http://www.popularmechanics.com/technology/gadgets/a23139/co...
I was so glad when I left REPLs and then debuggers and IDEs, to compiled languages and text-editor + command-line compilation and open source-code. I just cannot fathom the trend of the last 5 years where I hear and read people who cannot live without an IDE, a debugger and a REPL. It feels like living in a jail cell for me; a cell I left 20-25 years ago.
Incidentally, I just had to install an IDE today, for HDL development. I urgently have to find a way to turn the process into a Makefile or I will throw the computer through the window before the week-end comes.
Given, it can be unpleaseant in certain contexts, but Lisp has really good metaprogramming support, so you can add syntax for a runtime type system relatively simply. It's not optimal, and it certaibly isn't the fastest thing, but it works if you want it.
And nowadays, CL (and several of the Schemes) provide more powerful type systems, and sometimes even compile-time typing. All optional, of course.
Don't ask me: I'm using Chicken Scheme, which provides both (to an extent: The documentation is worryingly vague about how the type system handles failure).
But since you don't mind Lisp syntax, and really like type systems, have you tried Shen? (http://www.shenlanguage.org) it's an interesting project.
I also want:
(0) Type inference. Shen seems to require all typed code to be manually type-annotated.
(1) A decent module system. There seems to be no mention of abstract types in Shen's documentation.
(2) A guarantee that dynamically typed code won't break abstractions defined in statically typed code.
> Even ignoring the features that make it not so great a high-level language (such as the lack of a workable static semantics, forcing programmers to dynamically test what can be taken for granted in more civilized languages)
There are many more high-level features, than static semantics. Lisp is designed for runtime flexibility, not static semantics. Runtime flexibility allows lots of interesting high-level features.
> In the 80's, the only way one could reasonably hope to use Lisp as a systems language was to run it on hardware explicitly designed to run Lisp.
Lisp ran fine on five MIPS machines. Current systems have several hundred times the compute power.
The hardware was explicitly designed for Lisp in the late 70s when nothing comparable was available (single-user powerful workstations for AI/Math researchers developing large systems like Macsyma) and most development took place on time-shared mainframes with many users. The market that demanded this was a high-end market, thus the systems were developed, even though the were expensive.
Starting in the mid 80s, Common Lisp was moved to 1 to 10 MIPS workstations from SUN, SGI, Apollo, DEC, IBM, NeXT, Tectronix, and many others. The main problem at that time was that it would need 10 MByte RAM to run efficiently, which was expensive in the early 80s.
10 Mbyte. Today Lisp runs fine on an modern processor and some people tinker with Lisp-based operating systems, again.
> Of course, hardware support for features like dynamic typing and efficient dynamic allocation of lots of small objects doesn't come for free - this is what made Lisp machines expensive.
What made them expensive was the custom development for a small market, ten to twenty years ahead of their time. It was not the technology complexity of the hardware. People were buying future technology for lots of money, where comparable technology would appear 20 years later - the first Lisp Machines with object-oriented operating systems appeared in the late 70s for $100000 a piece.
Some of Lisp Machines used 8 bit ECC to provide error checking and correction, when memory wasn't that reliable. It made them even more expensive, because ECC memory was even more expensive than normal memory boards. With memory sizes from 4MB to 20MB.
Minor Lisp/Smalltalk support then went into the SPARC chips from SUN. Lisp ran quite well on those.
> dynamic typing and efficient dynamic allocation of lots of small objects
Every iPhone does that now, since Apple's Objective-C and the iOS frameworks are actually that: efficient dynamic allocation, of runtime-typed small and large objects.
If you want to see Lisp on a small machine, buy a Roomba cleaner.
http://www.irobot.com/For-the-Home/Vacuuming/Roomba.aspx
Their software was developed in Lisp already twenty years ago on tiniest hardware.
https://en.wikipedia.org/wiki/Roomba
L – A Common Lisp for Embedded Systems https://www.cs.cmu.edu/~chuck/pubpg/luv95.pdf
It will clean your home, using Lisp on tiny hardware.
My main desktop machine has 16 GB RAM, and its processor cycle speed is probably also 1600 times as much as that of a machine from back then. Why can't I get my computer to perform 1600 times as much concurrent work as back then?
> Every iPhone does that now, since Apple's Objective-C and the iOS frameworks are actually that: efficient dynamic allocation, of runtime-typed small and large objects.
That's a luxury that we can nowadays afford. Even the cheapest entry-level smartphone is ridiculously more powerful than an 80's era workstation. But the amount of work computers do for us hasn't grown proportionally to the amount of computing power.
Huh? I'm perfectly fine with abstractions. Just not with abstractions that are suboptimally designed and implemented.
> Don't forget to use as few abstractions as possible
Maybe you're confusing abstraction with runtime dispatch?
> every function call takes valuable time and computation.
Stroustrup's (whom otherwise I don't regard as a great language designer) principle applies: abstractions must elaborate to the best possibly code you would've manually written.
It's very simple:
(0) Runtime performance degradation is unacceptable.
(1) Increased compile times are annoying, but they're tolerable in exchange for more exhaustive automatic static analysis. Anything of interest that the compiler can't prove about my code, I would have to prove myself by hand anyway, which would take even more time than the slowest automated static analysis.
Requirements like these are not grounded in real software use.
In economic terms: a cheaper/slower software can be acceptable over a more expensive/faster software. Example: Java-based software vs. the same written in C++.
In reliability terms: a more robust / slower software can be acceptable over a more expensive/faster software. Example: Erlang-based software vs. C++ software on a network switch.
And so on.
Abstract/absolutist requirements like 'Runtime performance degradation is unacceptable' is not often found in actual software development/use.
Software usually has a multitude of different important qualities. Raw performance is just one of these. Especially one then needs to say what performance exactly and how to measure it. Performance can be measured in many ways: throughput, latency, micro-benchmark speed, etc. etc. Optimizing one (say, throughput) then has effects on others (say, latency). To achieve faster throughput, some functionality can be affected.
If runtime perf degradation is really unacceptable to you, you shouldn't be writing in functional languages, or even C. You should be writing in raw asm, as low-level as possible.
Now, this is a fairly trivial example, and not too hard for a compiler to optimize out, IIRC, but it's just an example. Other instances of this are far harder to optimize away.
The further up you are from the hardware, the less aware you are of the perf tradeoffs, and the less you can do to fix it. So if you want the best perf, go for asm.
In ML, most of the time I don't work with objects at all. I work with values. The beauty of programming with values is that the language implementor can represent them however he wishes, as long as he respects the semantics of the operations on said values. For example:
(0) Multiple logical nodes of a recursive data structure can be represented as a single physical node, eliminating the need to store pointers between them. For example, an ML implementation can determine that `List.tabulate (n, f)` always creates a list with `n` elements, and thus always pre-allocate a single large enough buffer before actually computing the elements. In Lisp, this wouldn't be a valid optimization, because Lisp mandates that every cons cell has its own object identity.
(1) Values with large representations can be automatically deduplicated, hash-consed or whatever it takes to reduce their memory footprint. Again, this is only possible because physical identities don't matter in ML (except for mutable cells and arrays).
In other words, values give the language implementation freedom to make useful physical data structure choices. Unlike objects, which come tied to a fixed representation.
There is no tradeoff between abstraction and efficiency, when you use the right abstractions.
Is that a real question?
> But the amount of work computers do for us hasn't grown proportionally to the amount of computing power.
I don't know about you, but my laptop is much faster than previous machines. Software which used to compile with the Lisp compiler in half an hour, now compiles in a few seconds.
This depends a lot on the user. In the right hands it's extremely efficient.
That said, you get a lot of similar behavior to this in things like Clojure + CIDER, but due to the nature of the underlying JVM, you still have to restart it every once in a while.
Part of me still wonders what we could come up with if we took the principles behind a true interactive IDE + REPL (like Emacs + CIDER) but removed the concept of "wrapping" a VM that was designed for a "programming language", and added the concept of a longer-lived "VM". Not quite what LispMs were, nor even Smalltalk (which relied too heavily on a GUI and a monolithic "image state" rather than individual persist-able files).
The DCEVM project almost removed any limitations on hotswapping code in the JVM until oracle hired the people behind it and then never integrated it into their JVM.
IMO the inefficiency is more due to the fundamental brokenness of the Unix process model. A Unix process is sort of like a VM with none of the security, and all of the stuff that has been added to it over the years (signals, IPC, dynamic linking) just makes it worse and worse. Things like full machine virtualization and now over the past 15 years namespaces/cgroups/containers are very complicated, brittle ways of trying to work around the shortcomings of Unix processes. This IMO is the biggest advance plan9 made over Unix.