Feed Aggregator Page 690
Rendered on Sat, 26 Aug 2023 01:39:19 GMT
Rendered on Sat, 26 Aug 2023 01:39:19 GMT
via Planet Lisp by on Wed, 26 Jul 2023 18:13:00 GMT
Follow me along this garden path (based on true events).
We have a nifty program and we want it to be flexible, so it has a config file. We make up some sort of syntax that indicates key/value pairs. Maybe we’re hipsters and use YAML. Life is good.
But we find that we to configure something dynamically, say based on the value of an environment variable. So we add some escape syntax to the config file to indicate that a value is a variable rather than a literal. But sometimes the string needs a little work done to it, so we add some string manipulation features to the escape syntax.
And when we deploy the program, we find that we’ve want to conditionalize part of the configuration based on the deployment, so we add a conditional syntax to our config language. But conditionals are predicated on boolean values, so we add booleans to our config syntax. Or maybe we make strings do double duty. Of course we need the basic boolean operators, too.
But there’s a lot of duplication across our configurations, so we add the ability to indirectly refer to other config files. That helps to some extent, but there’s a lot of stuff that is almost duplicated, except for a little variation. So we add a way to make a configuration template. Templating needs variables and quoting, so we invent a syntax for those as well.
We’re building a computer language by accident, and without a clear plan it is going to go poorly. Are there data types (aside from strings)? Is there a coherent type system? Are the variables lexically scoped? Is it call-by-name or call-by-value? Is it recursive? Does it have first class (or even second class) procedures? Did we get nested escaping right? How about quoted nested escaping? And good grief our config language is in YAML!
If we had some forethought, we would have realized that we were designing a language and we would have put the effort into making it a good one. If we’re lazy, we’d just pick an existing good language. Like Lisp.
via Planet Lisp by on Wed, 26 Jul 2023 00:00:00 GMT
DEFSECTION
needs to refer to definitions that do not create a
first-class object (e.g. stuff like (*DOCUMENT-LINK-TO-HYPERSPEC*
VARIABLE)
), and since its original release in 2014, a substantial
part of
PAX dealt
with locatives and references, which reify definitions. This release
finally factors that code out into a library called
DRef,
allowing PAX to focus on documentation. Being very young, DRef lives
under adult supervision, in a
subdirectory
of the PAX repository.
DREF> (definitions 'pax:document-object*)
(#<DREF DOCUMENT-OBJECT* GENERIC-FUNCTION>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (MGL-PAX-BLOG::CATEGORY T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (UNKNOWN-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (MGL-PAX::CLHS-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (MGL-PAX::INCLUDE-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (MGL-PAX::GO-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (GLOSSARY-TERM T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (SECTION T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (ASDF-SYSTEM-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (CLASS-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (STRUCTURE-ACCESSOR-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (WRITER-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (READER-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (ACCESSOR-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (METHOD-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (SETF-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (VARIABLE-DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (DREF T))>
#<DREF DOCUMENT-OBJECT* (METHOD NIL (T T))>)
DREF> (dref 'pax:document-object* '(method nil (class-dref t)))
#<DREF DOCUMENT-OBJECT* (METHOD NIL (CLASS-DREF T))>
DREF> (arglist *)
(DREF STREAM)
:ORDINARY
DREF> (docstring **)
"For definitions with a CLASS locative, the arglist printed is the
list of immediate superclasses with STANDARD-OBJECT, CONDITION and
non-exported symbols omitted."
DREF> (pax:document ***)
- [method] DOCUMENT-OBJECT* (DREF CLASS-DREF) STREAM
For definitions with a CLASS locative, the arglist printed is the
list of immediate superclasses with STANDARD-OBJECT, CONDITION and
non-exported symbols omitted.
During the refactoring, the references API was cleaned up. How to write extensions has seen lots of changes (see Extending DRef and Extending PAX), but normal use is the same. DRef is similar to Shinmera's Definitions library but is more tailored to the needs of PAX.
Also in this release:
Apropos got a detailed view feature, which includes the docstrings of all listed definitions not just the reference itself. This is very useful for getting an overview of a package.
The detailed view often has to render docstrings which have not been written with PAX in mind and are not proper markdown. These docstrings are now sanitized aggressively in a unavoidably heuristic manner.
There are now two supported CSS styles for HTML output: :DEFAULT
with sans-serif
, and :CHARTER
with Charter as the main
font (which is bundled). The :CHARTER
style is used in the linked
PAX World documentation on this blog. See
PAX:*BROWSE-HTML-STYLE*
and
PAX:UPDATE-ASDF-SYSTEM-HTML-DOCS.
As usual, quite a few bug fixes and some optimizations also found their way into this release.
via Planet Lisp by on Sat, 08 Jul 2023 17:21:00 GMT
Write a polemic about how omitting conditional expressions from a language is lazy and stupid.
Conditional expressions are a fundamental feature of any programming language that claims to be expressive, concise and elegant. They allow programmers to write complex logic in a single line, without the need for verbose and repetitive if-else statements. They also enable functional programming paradigms, such as pattern matching and recursion, that are essential for solving many problems efficiently and elegantly.
Omitting conditional expressions from a language is a sign of laziness and stupidity on the part of the language designers. It shows that they do not care about the needs and preferences of their users, who have to write more code, introduce more bugs, and suffer more frustration. It also shows that they do not understand the principles and benefits of functional programming, which have been proven by decades of research and practice. They are stuck in an imperative mindset, where everything has to be done with side effects and mutations.
A language without conditional expressions is a crippled language, that forces programmers to use hacks and workarounds to achieve what should be simple and natural. It is a language that discourages creativity, elegance and readability. It is a language that should be avoided by anyone who values their time, sanity and reputation.
Some examples of programming languages that don't have conditional expressions are:
- CoffeeScript
- Go
- MATLAB
- Pascal
- Rust
This isn't bad, but I wanted more vitriol. I asked and I got a screed that could have come out of comp.lang.functional Unfortunately, it thought better of it and erased its own output before I could snapshot it.
via Planet Lisp by on Wed, 28 Jun 2023 19:09:00 GMT
Here’s a function that fails a static type check, but has no runtime type error:
(defun foo () (sqrt (if (static-type-check? #’foo) "bogus" 2.0))
I suspect most people that favor static types will argue that this sort of program doesn’t count for some reason or other. I think this is more an example (albeit contrived) of the limitations of static type checking.
via Planet Lisp by on Tue, 27 Jun 2023 22:06:00 GMT
Many years ago I worked on a language called REBOL. REBOL was notable in that it used a variation of Polish notation. Function names came first, followed by the arguments in left to right order. Parentheses were generally not needed as the subexpression boundaries could be deduced from the arguments. It’s a bit complicated to explain, but pretty easy to code up.
An interpreter environment will be a lists of frames, and each frame is an association list of variable bindings.
(defun lookup (environment symbol) (cond ((consp environment) (let ((probe (assoc symbol (car environment)))) (if probe (cdr probe) (lookup (cdr environment) symbol)))) ((null environment) (error "Unbound variable.")) (t (error "Bogus environment.")))) (defun extend-environment (environment formals values) (cons (map ’list #’cons formals values) environment))
define
mutates the topmost frame of the environment.
(defun environment-define! (environment symbol value) (cond ((consp environment) (let ((probe (assoc symbol (car environment)))) (if probe (setf (cdr probe) value) (setf (car environment) (acons symbol value (car environment)))))) ((null environment) (error "No environment.")) (t (error "Bogus environment."))))
We’ll use Lisp procedures to represent REBOL primitives. The initial environment will have a few built-in primitives:
(defun initial-environment () (extend-environment nil ’(add lessp mult print sub sub1 zerop) (list #’+ #’< #’* #’print #’- #’1- #’zerop)))
A closure is a three-tuple
(defclass closure () ((arguments :initarg :arguments :reader closure-arguments) (body :initarg :body :reader closure-body) (environment :initarg :environment :reader closure-environment)))
An applicable object is either a function or a closure.
(deftype applicable () ’(or closure function))
We need to know how many arguments a function takes. We keep a table of the argument count for the primitives
(defparameter +primitive-arity-table+ (make-hash-table :test #’eq)) (eval-when (:load-toplevel :execute) (setf (gethash #’* +primitive-arity-table+) 2) (setf (gethash #’< +primitive-arity-table+) 2) (setf (gethash #’+ +primitive-arity-table+) 2) (setf (gethash #’- +primitive-arity-table+) 2) (setf (gethash #’1- +primitive-arity-table+) 1) (setf (gethash #’print +primitive-arity-table+) 1) (setf (gethash #’zerop +primitive-arity-table+) 1) ) (defun arity (applicable) (etypecase applicable (closure (length (closure-arguments applicable))) (function (or (gethash applicable +primitive-arity-table+) (error "Unrecognized function.")))))
REBOL-EVAL-ONE
takes a list of REBOL expressions and
returns two values: the value of the leftmost expression in the
list, and the list of remaining expressions.
(defun rebol-eval-one (expr-list environment) (if (null expr-list) (values nil nil) (let ((head (car expr-list))) (etypecase head ((or number string) (values head (cdr expr-list))) (symbol (case head (define (let ((name (cadr expr-list))) (multiple-value-bind (value tail) (rebol-eval-one (cddr expr-list) environment) (environment-define! environment name value) (values name tail)))) (if (multiple-value-bind (pred tail) (rebol-eval-one (cdr expr-list) environment) (values (rebol-eval-sequence (if (null pred) (cadr tail) (car tail)) environment) (cddr tail)))) (lambda (values (make-instance ’closure :arguments (cadr expr-list) :body (caddr expr-list) :environment environment) (cdddr expr-list))) (otherwise (let ((value (lookup environment head))) (if (typep value ’applicable) (rebol-eval-application value (cdr expr-list) environment) (values value (cdr expr-list)))))))))))
If the leftmost symbol evaluates to something applicable, we find out how many arguments are needed, gobble them up, and apply the applicable:
(defun rebol-eval-n (n expr-list environment) (if (zerop n) (values nil expr-list) (multiple-value-bind (value expr-list*) (rebol-eval-one expr-list environment) (multiple-value-bind (values* expr-list**) (rebol-eval-n (1- n) expr-list* environment) (values (cons value values*) expr-list**))))) (defun rebol-eval-application (function expr-list environment) (multiple-value-bind (arglist expr-list*) (rebol-eval-n (arity function) expr-list environment) (values (rebol-apply function arglist) expr-list*))) (defun rebol-apply (applicable arglist) (etypecase applicable (closure (rebol-eval-sequence (closure-body applicable) (extend-environment (closure-environment applicable) (closure-arguments applicable) arglist))) (function (apply applicable arglist))))
Evaluating a sequence is simply calling rebol-eval-one
over and over until you run out of expressions:
(defun rebol-eval-sequence (expr-list environment) (multiple-value-bind (value expr-list*) (rebol-eval-one expr-list environment) (if (null expr-list*) value (rebol-eval-sequence expr-list* environment))))
Let’s try it:
(defun testit () (rebol-eval-sequence ’( define fib lambda (x) (if lessp x 2 (x) (add fib sub1 x fib sub x 2)) define fact lambda (x) (if zerop x (1) (mult x fact sub1 x)) define fact-iter lambda (x answer) (if zerop x (answer) (fact-iter sub1 x mult answer x)) print fib 7 print fact 6 print fact-iter 7 1 ) (initial-environment))) CL-USER> (testit) 13 720 5040
This little interpreter illustrates how basic REBOL evaluation works. But this interpreter doesn’t support iteration. There are no iteration special forms and tail calls are not “safe for space”. Any iteration will run out of stack for a large enough number of repetition.
We have a few options:
do
, repeat
, loop
, for
, while
, until
etc.
To effectively support continuation passing style, you need tail recursion. This alone is a pretty compelling reason to support it.
But it turns out that this is easier said than done. Are you a cruel TA? Give your students this interpreter and ask them to make it tail recursive. The problem is that key recursive calls in the interpreter are not in tail position. These are easy to identify, but you’ll find that fixing them is like flattening a lump in a carpet. You’ll fix tail recursion in one place only to find your solution breaks tail recursion elsewhere.
If our interpreter is written in continuation passing style, it
will be syntactically tail recursive, but it won’t be
“safe for space” unless the appropriate continuations
are η-reduced. If we look at the continuation passing style
version of rebol-eval-sequence
we’ll see a
problem:
(defun rebol-eval-sequence-cps (expr-list environment cont) (rebol-eval-one-cps expr-list environment (lambda (value expr-list*) (if (null expr-list*) (funcall cont value) (rebol-eval-sequence-cps expr-list* environment cont)))))
We cannot η-reduce the continuation. We cannot make this “safe for space”.
But the continuation contains a conditional, and one arm of the
conditional simply invokes the containing continuation, so we can
η-convert this if we unwrap the conditional. We’ll do
this by passing two continuations to rebol-eval-one-cps
as follows
(defun rebol-eval-sequence-cps (expr-list environment cont) (rebol-eval-one-cps expr-list environment ;; first continuation (lambda (value expr-list*) (rebol-eval-sequence-cps expr-list* environment cont)) ;; second continuation, eta converted cont))
rebol-eval-one-cps
will call the first continuation if
there are any remaining expressions, and it will call the second
continuation if it is evaluating the final expression.
This intepreter, with the dual continuations
to rebol-eval-one-cps
, is safe for space, and it will
interpret tail recursive functions without consuming unbounded stack
or heap.
But we still have a bit of an implementation problem. We’re allocating an extra continuation per function call. This doesn’t break tail recursion because we discard one of the continuations almost immediately. Our continuations are not allocated and deallocated in strict stack order anymore. We cannot easily convert ths back into a stack machine implementation.
To solve this problem, I rewrote the interpreter using Henry Baker’s Cheney on the M.T.A technique where the interpreter functions were a set of C functions that tail called each other and never returned. The stack would grow until it overflowed and then we’d garbage collect it and reset it. The return addresses pushed by the C function calls were ignored. Instead, continuation structs were stack allocated. These contained function pointers to the continuation. Essentially, we would pass two retun addresses on the stack, each in its own struct. Once the interpreter figured out which continuation to invoke, it would invoke the function pointer in the struct and pass a pointer to the struct as an argument. Thus the continuation struct would act as a closure.
This technique is pretty portable and not too bad to implement, but writing continuation passing style code in portable C is tedious. Even with macros to help, there is a lot of pointer juggling.
One seredipitous advatage of an implementation like this is that first-class continuations are essentially free. Now I’m not wedded to the idea of first-class continuations, but they make it much easier to implement error handling and advanced flow control, so if you get them for free, in they go.
With it’s Polish notation, tail recursion, and first-class continuations, REBOL was described as an unholy cross between TCL and Scheme. “The result of Outsterhout and Sussman meeting in a dark alley.”
Current versions of REBOL use a simplified interpreter that does not support tail recursion or first-class continuations.
via Erlang/OTP | News by Henrik Nord on Tue, 16 May 2023 00:00:00 GMT
Erlang/OTP 26 is a new major release with new features, improvements as well as a few incompatibilities.via Erlang/OTP | News by Henrik Nord on Wed, 12 Apr 2023 00:00:00 GMT
Erlang/OTP 26.0-rc3 is the third and last release candidate before the OTP 26.0 release. The release candidate 3 fixes some bugs found in the first two release candidates.via Erlang/OTP | News by Henrik Nord on Wed, 22 Mar 2023 00:00:00 GMT
OTP 26.0-rc2via Erlang/OTP | News by Henrik Nord on Wed, 08 Mar 2023 00:00:00 GMT
OTP 25.3via Erlang/OTP | News by Henrik Nord on Wed, 15 Feb 2023 00:00:00 GMT
OTP 26.0-rc1via Lispjobs by halmonster on Fri, 13 Jan 2023 19:36:24 GMT
Job posting: https://jobs.lever.co/dodmg/85221f38-1def-4b3c-b627-6ad26d4f5df7?lever-via=CxJdiOp5C6 HRL has been on the leading edge of technology, conducting pioneering research and advancing the state of the art. This position is integrated with a growing team of scientists and engineers on HRL’s quantum computing research program. GENERAL DESCRIPTION: As a DevOps/DevSecOps engineer, you’ll be focused on maintaining reliable systems for testing […]via Erlang/OTP | News by Henrik Nord on Wed, 14 Dec 2022 00:00:00 GMT
OTP 25.2via Erlang/OTP | News by Henrik Nord on Wed, 21 Sep 2022 00:00:00 GMT
OTP 25.1via Lispjobs by halmonster on Wed, 20 Jul 2022 15:46:30 GMT
More info here https://jobs.3e.eu/en/vacature/74459/senior-common-lisp-developer/ Writing elegant, clean code is your thing and you are not afraid to pick things up fast and be proactive in a team. Someone creative and innovative is welcome – we embrace new technologies where they can be applied and make sense. The ideal candidate has the following qualifications: Master’s degree […]via Erlang/OTP | News by Henrik Nord on Wed, 18 May 2022 00:00:00 GMT
Erlang/OTP 25 is a new major release with new features, improvements as well as a few incompatibilities.via Erlang/OTP | News by Henrik Nord on Wed, 13 Apr 2022 00:00:00 GMT
OTP 25-rc3via Erlang/OTP | News by Henrik Nord on Wed, 23 Mar 2022 00:00:00 GMT
OTP 25-rc2via Elm - Latest posts by @dillonkearns Dillon Kearns on Mon, 28 Feb 2022 16:14:35 GMT
Hey everyone, we have a new Elm Radio episode today about the Primitive Obsession code smell. We break down the process of extracting types with semantic meaning, and how those types make your code easier to work with.
Hope you enjoy the episode!
via Elm - Latest posts by @passiomatic Andrea Peltrin on Mon, 28 Feb 2022 15:45:23 GMT
Hi! Just a quick post to let you know that a new version of Elm Designer—the visual code generator for Elm UI—is out and now it is a web app: elm-designer.passiomatic.com
Next big update will be add a server-side auth system and will allow to save your data on the server.
I hope you will find Elm Designer useful!
via Planet Lisp by on Mon, 28 Feb 2022 10:23:03 GMT
Just a quick update about Kandria: there'll be a free demo available on Steam tomorrow, 1st of March. The demo will go live at noon GMT! It has a number of improvements from the old demos, not just limited to bug fixes. I hope you give it a look and wishlist it on Steam!