That would be true for source level interpreters, but not for compiled implementations.
1 karma · joined February 12, 2025
That would be true for source level interpreters, but not for compiled implementations.
When Scheme was developed, it was special, because it used lexical binding and provided lexical closures. That was new. Writing functions that write functions would not have been new, since LISP had that already -> including Maclisp, which was used for the first SCHEME implementation. Later Scheme got used for exploring different macro systems.
That's true, but it sounds a bit as if Lisp interpreters are something very unusual. The syntax and other details may differ, but the general idea of executing source code via an interpreter is very old, often implemented - similar also the idea that the code can be mutable. It's just not very fashionable, since some Lisp dialects are designed such that one wants the compiler to be able to statically check the code for various things, before runtime.
In Common Lisp we would not want to introduce variables into an outer scope by enclosed functions. One would explicitly set up a scope.
Example: this is a macro example, which creates a scope, where the matching match-variables are also Lisp variables.
CL-USER 165 > (pmatch:when-match (append (coerce "hello" 'list)
'(#$a)
(coerce "ld" 'list))
(coerce "helloworld" 'list)
(length a))
3What are "unstable electricity bills" ? Changing market prices are the reality in many markets. The European reality is clear: more connection between local energy markets creates a larger EU market and this allows to trade electricity based on demand and supply in a very large market.
Germany already exchanges electricity with Sweden via Denmark. Do you have other examples? Germany directly exchanges electricity with lots of other countries already.
Renewable energy from Wind and Solar is growing in many places in Europe. That's not a special German thing, it's just that Germany started earlier.
EU: https://www.eea.europa.eu/en/topics/in-depth/renewable-energ...
"EU Member States need to massively ramp up their renewable power capacity in the coming years. This increase in renewables from variable sources, such as wind and solar, will also increase the need for ‘flexibility’ in the EU electricity system."
"The share of renewables in Europe is expected to keep growing. However, meeting the new target of 42.5% for 2030 will demand more than doubling the rates of renewables deployment seen over the past decade, and requires a deep transformation of the European energy system."
A graph of the EU development is here:
https://flo.uri.sh/visualisation/16410231/embed?auto=1
Compare with Germany:
https://www.cleanenergywire.org/sites/default/files/styles/p...
What you think is a German problem, is a reality and a trend in Europe.
A deep transition to renewable energy is also world-wide the only viable way to a cleaner electricity production.
Sweden could be in a good position, since it and surrounding countries have lots of capacity for wind generated electricity and hydro based electricity generation/storage.
https://www.euronews.com/business/2023/10/03/3-trillion-is-f...
While the Government rises subsidies to keep electricity bills down?
https://www.lemonde.fr/en/energies/article/2023/04/21/france...
"Increases in electricity bills have been less sharp in France thanks to government subsidies, at 4% in 2022 and 15% this year – at a projected cost of €45 billion in 2023."
In the meantime, most of the french nuclear industry has been taken over by the government to prevent its financial collapse.
For example this article from 2022 paints a much worse picture on why EDF can't increase prices to cover the costs: https://www.reuters.com/business/energy/why-french-plan-take...
CL-USER 130 > (defun myfun (d) (print "heyo") (print d))
MYFUN
CL-USER 131 > (let ((source (function-lambda-expression #'myfun)))
(mapcar (eval (list* 'lambda
(second source)
(nthcdr 4 source)))
'("world")))
"heyo"
"world"
("world")
Above can in some ways done in many Lisp implementations. It's in this form simply not widely used. For most applications it's more interesting to use macros to manipulate code, which then can be compiled to efficient code.Pattern matching is much implemented in Lisp.
I had adopted this code for a pattern matcher from a book (LISP, Winston/Horn), probably >30 years ago:
CL-USER 115 > (pmatch:match '(#$a is #$b) '(this is a test))
((B (A TEST)) (A (THIS)))
CL-USER 116 > (pmatch:match '(#$X (d #$Y) #$Z) '((a b c) (d (e f) g) h i))
((Z (H I)) (Y ((E F) G)) (X ((A B C))))
Not to say, that Picolisp isn't great for you, but it is not the only language where lists can be manipulated.That's why EDF wants money from the british tax payer for the construction of nuclear power plant?
EDF also wants higher electricity prices, the French government doesn't:
https://www.ft.com/content/eaee5a3d-ba76-40ba-8300-082f58657...
" In particular, a looming overhaul of the way nuclear power prices are regulated in France has created fractures as EDF seeks higher prices to bring in much-needed capital, while the state wants to contain energy costs for households and businesses as much as possible, the people added.
“For Rémont, electricity prices need to be sufficiently high so that the group can invest, and the government needs to have prices that are acceptable to consumers,” another person said.
At its core, the debate around EDF is an existential one — whether its executives can and should run the group as a normal company despite it being state-owned. "
I would also see as requirements: It must be cost effective, it must be scalable in several dimensions: fast to plan and approve, fast to build, able to be build in massive numbers, able to be financed in large numbers, able to be operated in large numbers, able to cool in large numbers, able to operate in a secure environment, ...
By passing the true costs to the tax payer and the next generations.
CL-USER 100 > (defun unroll (n exp) `(lambda () ,(loop repeat n collect exp)))
UNROLL
CL-USER 101 > (unroll 3 '(foo))
(LAMBDA NIL ((FOO) (FOO) (FOO)))
CL-USER 102 > (eval *)
#<anonymous interpreted function 8020000EC9>
CL-USER 103 > (describe *)
#<anonymous interpreted function 8020000EC9> is a TYPE::INTERPRETED-FUNCTION
CODE (LAMBDA NIL ((FOO) (FOO) (FOO)))
As you can see, the thing is basically the same as a cons cell with two entries the type and the code: (TYPE::INTERPRETED-FUNCTION . (LAMBDA NIL ((FOO) (FOO) (FOO))))
The above Lisp implementation does not use a cons cell, but a different type mechanism to easily and reliably identify the runtime type.I picolisp this is hardwired into the interpreter. The interpreter will also need to check every time at runtime, if the list structure is actually a function and what kind of function it is.
In above Lisp, the type of the function is encoded during EVAL and the check for the type is then a type tag check.
for this example here, using the LispWorks implementation, it also makes no difference for EVAL if it is a function with 10 or with 100000 subforms. The execution time is small. No special processing of the list of subforms takes place. For example the code is not compiled, not converted to byte code, not converted to another representation.
CL-USER 111 > (let ((f (unroll 10 '(foo)))) (time (eval f)))
Timing the evaluation of (EVAL F)
User time = 0.000
System time = 0.000
Elapsed time = 0.000
Allocation = 184 bytes
0 Page faults
GC time = 0.000
#<anonymous interpreted function 8020001DA9>
CL-USER 112 > (let ((f (unroll 100000 '(foo)))) (time (eval f)))
Timing the evaluation of (EVAL F)
User time = 0.000
System time = 0.000
Elapsed time = 0.000
Allocation = 184 bytes
0 Page faults
GC time = 0.000
#<anonymous interpreted function 8020000839>
CL-USER 113 > (defun unroll (n exp) `(lambda () ,(loop repeat n collect exp)))
UNROLL CL-USER 63 > (defun foo (a) (print 'hey) (print a))
FOO
CL-USER 64 > (foo 'jack)
HEY
JACK
JACK
CL-USER 65 > (function-lambda-expression 'foo)
(LAMBDA (A) (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 81D03EFF03>)) (DECLARE (LAMBDA-NAME FOO)) (PRINT (QUOTE HEY)) (PRINT A))
NIL
FOO
CL-USER 66 > (fifth *)
(PRINT (QUOTE HEY))
CL-USER 67 > (setf (fifth (function-lambda-expression 'foo)) '(print 'hello))
(PRINT (QUOTE HELLO))
CL-USER 68 > (foo 'jack)
HELLO
JACK
JACKYou can doubt that. But I have done it. I know that it works.
CL-USER 63 > (defun foo (a) (print 'hey) (print a))
FOO
CL-USER 64 > (foo 'jack)
HEY
JACK
JACK
CL-USER 65 > (function-lambda-expression 'foo)
(LAMBDA (A) (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 81D03EFF03>)) (DECLARE (LAMBDA-NAME FOO)) (PRINT (QUOTE HEY)) (PRINT A))
NIL
FOO
CL-USER 66 > (fifth *)
(PRINT (QUOTE HEY))
CL-USER 67 > (setf (fifth (function-lambda-expression 'foo)) '(print 'hello))
(PRINT (QUOTE HELLO))
CL-USER 68 > (foo 'jack)
HELLO
JACK
JACK
> E.g. both CLISP and guile error out if I try `(car (lambda (a b) (+ a b)))`.Sure, but in CLISP the function is still a list internally. An interpreted function is a record, which stores the code as a list internally. The internally stored list is interpreted.
Python compiles the code to byte code. CLISP has both a list-based interpreter and a byte code interpreter.
Strange, I thought many Lisp systems still have source level list-based interpreters. For Common Lisp I would think: SBCL (optional), CLISP, Allegro CL, LispWorks, ECL, ... They can also compile code. Compiling Lisp code was also already available in the first Lisp implementations and having a compiler was an explicit goal of the original implementors.
Let's use the LispWorks Listener (the REPL tool):
CL-USER 25 > (defun foo (bar) (break) (print (list :hello bar)))
FOO
CL-USER 26 > (foo 10)
Break.
1 (continue) Return from break.
2 (abort) Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 27 : 1 > :bq
INVOKE-DEBUGGER <- BREAK <- FOO <- EVAL <- CAPI::CAPI-TOP-LEVEL-FUNCTION <- CAPI::INTERACTIVE-PANE-TOP-LOOP
<- MP::PROCESS-SG-FUNCTION
CL-USER 28 : 1 > :n
Call to INVOKE-DEBUGGER
CL-USER 29 : 1 > :n
Call to BREAK
CL-USER 30 : 1 > :n
Interpreted call to FOO
CL-USER 31 : 1 > :lambda
(LAMBDA (BAR) (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 81D03EFF03>)) (DECLARE (LAMBDA-NAME FOO)) (BREAK) (PRINT (LIST :HELLO BAR)))
The debugger output of the currently running function looks like a linked list to me. I could modify it destructively, if I wanted.LispWorks has a list-level interpreter. One can also compile code.
Typically Lisp interpreters tend to be written in C, since that usually is more portable than Assembler.
> On the downside, this is all of course completely unchecked and pretty unsafe, and, to come back to the original point, you do not have such conveniences as lexical scoping.
I would expect from a typical Lisp interpreter (a source level list-based interpreter) that it does all kinds of runtime checks and also provides lexical scoping. If there is a clojure, then this closure would be a combination of some function and an environment. In standard Common Lisp there is no access to that environment, but I could look from an inspector into it:
CL-USER 54 > (defun example (a) (lambda () (break) (print a)))
EXAMPLE
CL-USER 55 > (example 10)
#<anonymous interpreted function 8020001A59>
CL-USER 56 > (describe *)
#<anonymous interpreted function 8020001A59> is a TYPE::INTERPRETED-FUNCTION
Code (LAMBDA NIL (BREAK) (PRINT A))
Environment ((A . 10) (#:SOURCE-LEVEL-ENVIRONMENT-MARKER FUNCTION NIL . #<EQ Hash Table{0} 81D03EFF03>) (#:FUNCTOR-MARKER LAMBDA (A) (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 81D03EFF03>)) (DECLARE (LAMBDA-NAME EXAMPLE)) (LAMBDA NIL (BREAK) (PRINT A))))
As we can see, the interpreted closure has code a list and an environment, where A = 10.Here follows an excerpt from a paper "Some History of Functional Programming Languages" by D. A. Turner. It talks about LISP, as invent/discovered by John McCarthy:
https://www.cs.kent.ac.uk/people/staff/dat/tfp12/tfp12.pdf
-----
Some Myths about LISPSomething called “Pure LISP” never existed — McCarthy (1978) records that LISP had assignment and goto before it had conditional expressions and recursion — it started as a version of FORTRAN I to which these latter were added. LISP 1.5 programmers made frequent use of setq which updates a variable and rplaca, rplacd which update the fields of a CONS cell.
LISP was not based on the lambda calculus, despite using the word “LAMBDA” to denote functions. At the time he invented LISP, McCarthy was aware of (Church 1941) but had not studied it. The theoretical model behind LISP was Kleene’s theory of first order recursive functions.
The M-language was first order, as already noted, but you could pass a function as a parameter by quotation, i.e. as the S-expression which encodes it. Unfortunately, this gives the wrong binding rules for free variables (dynamic instead of lexicographic).
If a function has a free variable, e.g y in
f = λx.x + y
y should be bound to the value in scope for y where f is defined, not where f is called.McCarthy (1978) reports that this problem (wrong binding for free variables) showed up very early in a program of James Slagle. At first McCarthy assumed it was a bug and expected it to be fixed, but it actually springs from something fundamental — that meta-programming is not the same as higher order programming. Various devices were invented to get round this FUNARG problem, as it became known.
Not until SCHEME (Sussman 1975) did versions of LISP with default static binding appear. Today all versions of LISP are lambda calculus based.
-----
A remark from me:"Today all versions of LISP are lambda calculus based.", except where they are not, like evaluation rules, dynamic binding, data types, etc.
What we have now in most Lisps since the mid 80s is lexical binding and closures, but not exclusively. Scheme earlier called dynamic bound variables "fluids". CL has it, for example by default for global variables.
> internal compiler is pretty capable
Depends, when reading the documentation, one gets the impression that their compiler is very limited.
Noteworthy: CLX is written in Lisp and does not use Xlib (which is a C library).
> astonishingly similar semantics
The "Wolfram language" has at its core a rewrite rule systems. Expressions are being rewritten by applying transformations to it. Lisp does not use anything like that. Lisp has an evaluator mechanism, based on fixed evaluation rules (+ macro transformations, which are again Lisp functions).
As a result, code in the Wolfram language is difficult to (fully) compile. Good and extensive Lisp compilers exist since the early 60s. Current examples of complete compilers are SBCL for Lisp and Chez Scheme for Scheme.
The Wolfram language also has no formal spec (compare to something like Scheme) and the language itself is not open sourced, including its main implementation. It's basically defined by its main implementation, while its proprietary language documentation looks like written in a such way to prevent implementations of the language.
This is an example of an M-Expression in the original definition of Lisp:
[eq[third[(A B C (D . E))];C]→cons[D;cdr[((A 1 2 3) B C)]; T→car[x]]
which is roughly equivalent to the Lisp S-Expression (COND ((EQ (THIRD (QUOTE (A B C (D . E)))) (QUOTE C))
(CONS (QUOTE D) (CDR (QUOTE ((A 1 2 3) B C)))))
(T (CAR X)))
Which evaluates to (D B C):-)
> was also quite impressed with SBCL for catching function calls passing unknown keys at compile time, before the call is invoked.
Generally CL compilers tend to check argument lists at compile time. Number of args, correct keyword arguments, ...
SBCL is especially good, due to its further support of declarations as assertions and its support for various compile time checks. You'll also get Lisp backtraces in a natively compiled Lisp then as a bonus. Also for newcomers it is quite helpful, because SBCL gives a lot of warnings and other feedback for various possible problems (from undeclared identifiers, unused variables up to missing optimization opportunities).
> Perhaps the fact that many Lisp guides feel compelled to start with a terse implementation of lambda calculus might actually be somewhat of a disservice, in hiding the more practical side of the language?
That's true. Lisp was often used in education as a vehicle to learn things like lambda calculus (or similar). Practical programming or "software engineering" with Lisp wasn't part of those courses.
There are books which cover those topics, too. Like "Practical Common Lisp" by Peter Seibel, "Paradigms of AI Programming" from Peter Norvig or "Common Lisp Recipes" by Edi Weitz.
For SBCL one definitely needs to read the manual to get an idea about its extended features.
"hardware" in quotes. A bunch of the machines had no/little language specific hardware. For example an Xerox Interlisp-D machine was the same hardware like the Smalltalk or Mesa system. The microcode was different. The microcode provided the instruction set and then the machine would boot either into the corresponding operating systems for Interlisp, Smalltalk or Mesa. https://en.wikipedia.org/wiki/Xerox_Star
Similar for the MIT CADR and some others, it was also a microprogrammed 32bit CPU.
Symbolics' CPU were also microprogrammed, but they added hardware features to it.
The SPUR (which is a RISC chip) mentioned is a more generic design, but with support for languages features for Lisp. There were other chips in the making at that time, like the Symbolics Sunstone CPU, which was also a RISC design for Lisp, but which also did not reach the market.
> Take Java for an example: it's possible to implement the JVM as a chip, but then you have a stack machine, and you can't JIT it onto a register architecture.
One could do that, but it would be a more complex chip.
For Lisp CPUS "fast" for benchmarks was also not that much a goal. Goals oftenwere "fast" execution of a Lisp operating system (written in a dynamically typed and garbage collected language), support for large address spaces, support for Lisp data types&data representation, generic operations (like generic arithmetic operations) and compact machine code.
It's actually very different to 'read source code and use batch compilation', from 'interactively exploring the source code and the running program at the same time'.
Relatively typical is the preference for long and descriptive names in larger software bases, with lots of documentation strings and named arguments.
* Development environments come with many introspection capabilities: describe, documentation, inspect, break, ...
* There are standard features for built in documentation strings for functions, variables, macros, classes, ...
* Macros allow very descriptive code. One can extended the language such that the constructs are very descriptive and declarative.
* Macros allow embedded domain specific code, which makes the code very readable, and gets rid of unnecessary programming details.
* Symbols can get arbitrary long and can contain arbitrary identifiers.
* Functions often have named parameters. Source code typical makes extensive use of named parameters.
* Details like manual memory management are not needed. -> code is simplified
* Many language constructs have an explicit and tight scope. -> for examples variables can't be introduced in arbitrary places in a scope.
* The language standard is very stable.
* Language extension is built-in (macros, reader, meta-object protocol, ...) and everyone uses the same mechanisms, with full language support in the extensions. -> no need tof additional and external macro processors, templating engine, XML engines, ...
* Users can more easily share/improve/collect deep language extensions, without the need to hack specific compiler implementation details, since the extension language is Lisp itself.
* Typical code is not using short identifiers or one letter identifiers with a complex operator hierarchy.
* Development is typically interactive, where one loads a program into Lisp and then one can query the Lisp system about the software (edit, who-calls, graph classes, show documentation, ...). Thus the developer does not work only with text, but can live interact and inspect the software, which is always in a debug mode.
* The code can contain examples and tests, which can be immediately tried out by a programmer while reading the code.
* There is a standardized language with widely different implementations. For collaboration it is can be very helpful that even then much of the core code can be shared, instead of having to reinvent the wheel for those different environments. The Lisp code can query the runtime and adapt itself to the implementation. Other systems have that too with an extra external configuration tools. Often it is possible for a different user that shipped source changes can be loaded into a running software. It is then immediately active and information about argument lists, documentation, class hierarchies, etc. is instantly updated.
Here is an example for a interactive definition of a function with documentation, type declarations and named arguments.
CL-USER 12 > (defun some-example-for-hackernews (&key author to title text)
(declare (type symbol author to)
(type list text))
"This code is an example for Hackernews, to show off readability features."
(print (list author 'writes 'to to))
(print (list 'title 'is title))
(print text)
(values))
SOME-EXAMPLE-FOR-HACKERNEWS
CL-USER 13 > (some-example-for-hackernews
:author 'lispm
:to 'troad
:title 'lisp-features
:text '("example for a function with documentation, type declaration and named arguments"))
(LISPM WRITES TO TROAD)
(TITLE IS LISP-FEATURES)
("example for a function with documentation, type declaration and named arguments")
CL-USER 14 > (documentation 'some-example-for-hackernews 'function)
"This code is an example for Hackernews, to show off readability features."
Another example: DEFCLASS is a macro for defining classes. Again, documentation and introspection is built-in.
The developer does not need to read and work with dead text, but can interactively explore and try out the software, while using self-documentation features. As one can see the macro uses similar named argument lists as functions. There is a slot named WARP-CLASS and arguments for types, initialization arguments, documentation, and so on. The macro then expand this form to larger code and saves the user a lot of typing. The language can use similar mechanisms to be extend with other features, without the need to go into compiler hacking. Thus language extensions can be written and documented by users in a standard way, which greatly enhances the way how to use and understand language extensions. CL-USER 31 > (defclass space-ship ()
((name :type 'string :initarg :name :documentation "The space ship name")
(warp-class :type 'number :initarg :warp-class :documentation "The warp class describes the generation of the warp propulsion system. 1 is the slowest and 5 is the fastest")
(warp-speed :type 'number :initform 0 :documentation "The current warp speed"))
(:documentation "this class describes space ships with warp propulsion"))
#<STANDARD-CLASS SPACE-SHIP 8220381C2B>
CL-USER 32 > (make-instance 'space-ship
:name "Gondor"
:warp-class 3)
#<SPACE-SHIP 8010170AE3>
CL-USER 33 > (describe *)
#<SPACE-SHIP 8010170AE3> is a SPACE-SHIP
NAME "Gondor"
WARP-CLASS 3
WARP-SPEED 0
CL-USER 34 > (documentation 'space-ship 'type)
"this class describes space ships with warp propulsion"Design Decisions in SPUR: https://pages.cs.wisc.edu/~markhill/papers/computer86_spur.p...
SPUR: A VLSI Multiprocessor Workstation: https://www2.eecs.berkeley.edu/Pubs/TechRpts/1986/CSD-86-273...
Multiprocessing extensions in Spur Lisp: https://ieeexplore.ieee.org/document/31651
There are lots of programming languages which are "uniquely powerful": C++, Prolog, Haskell, ...
> Lisp is a uniquely flexible language
I'm not sure if I buy "uniquely", but "very" would be fine.
> Uniquely flexible languages impose a cost for readability and collaboration.
At the same time it provides also important features for readability and collaboration. There are code bases of complex Lisp software, which are maintained by small&changing teams for several decades.
Lisp is effective not so much for "lone wolfs", but for small teams (5 to 100 people) working in a shared infrastructure with larger groups. Example: SBCL is a complex Common Lisp implementation, which goes back to the early 80s (-> Spice Lisp). SBCL is maintained by a group of people and has monthly releases. Around it there is an eco-system of software.
Simpler Lisp dialects can also be effective for larger groups. For example there are many people using "AutoLisp" (or versions of it), a simple Lisp dialect for scripting AutoCAD (and various competitors).
IMHO PAIP is extremely well written. Book and Code: https://github.com/norvig/paip-lisp
SICP has a mixed reception. Many find it excellent, others find it to focused to teach CS concepts in a math-oriented way. Thus there have been written several alternatives to SICP, using Scheme, Logo, other Functional Programming Languages, ... I think it's a great book, with a lot of excellent books written as a reaction to it.
I care about renewable energy (for example for many years my electricity contract is from a local provider of 100% renewable energy).
Nuclear energy will save much less lives, than a rapid expansion of renewable energy, which is the only viable way to make a large impact on global CO2 emissions. I also think that Nuclear energy is inherently harmful in general, such that it best is avoided, especially given that it does not scale for the challenges ahead.
Here is a task for you: xy graph the following, on a time axis: share of nuclear powered electricity from the last 20 years and the yearly electricity production.
Then graph the yearly renewable energy installations and produced electricity over that period.
Then compare.
With a little math knowledge you can deduce that the situation to use nuclear energy for reducing CO2 worldwide is hopeless, renewable has been scaling much quicker and will accelerate even more in the current future.
The numbers you can collect are mostly facts and then you can find out for yourself which technology scales better. You would not fall victim to desinformation, if you collect reliable data and try to analyze it. There is only a little math needed.
Good luck!