Skip to content

Latest commit

 

History

History
304 lines (234 loc) · 9.49 KB

README.org

File metadata and controls

304 lines (234 loc) · 9.49 KB

clues - Common Lisp Useful(?) Examples/Exemplars

Hopefully they’re useful!

This repo will contain examples (and exemplars) of what I think is useful lisp code. It will mainly be common lisp, but emacs lisp may also appear, and I guess any other lisp-y type languages (i.e. scheme).

The plan is that when I find something useful I will add it to this file in the Blog Ideas section and then, at some point in the future, I will write it up in a blog-post style. The blog posts will appear in my blog at https://stewart123579.github.io/blog/tags/clues

This file and blogging

I write all of my posts (and almost everything else) using Emacs. In particular I use Org Mode, and I’ve recently discovered the useful ox-hugo minor mode to export the pages using the Hugo engine.

clues

There are my various CLUEs (Common Lisp Useful Examples/Exemplars) posts.

The orgmode (i.e. text) file that they are generated from is available: https://github.com/stewart123579/clues

Posts [3/3]

Quicklisp

Update list of local projects for Quicklisp

I always forget how to update the list of local projects for Quicklisp.

(ql:register-local-projects)

I think I learned about it from Wimpie Nortje’s post.

Loops in Lisp

There are two type of lisp coders. Those that love the loop macro and those that don’t.

I’m one of the former.

Take this beautiful construction:

(loop for item in mylist
      collect (first item) into known-keys
      maximize (second item) into big
      minimize (second item) into small
      finally (return (values known-keys big small)))

Even not knowing what mylist was you could guess that known-keys would be the collection of all the first elements of item, and big and small would be the largest and smallest values in the second part of item.


If I told you that mylist was a list of items that had two elements, viz.

((a . 3) (b . 2) (c . 1))

You’d be able to guess that the result would be something like

(a b c)
3
1

loop is easy to read and reflects the true strength of common lisp - DSLs make readable (and performant) code easy.

Removing functions and variables in lisp

:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :math true

Here’s how to remove variables and functions from an image:

(makunbound 'variable)

(fmakunbound 'function)

So, for example:

(defvar foo 42)
(defun bar () (print "Hello world!"))

(boundp 'foo)                           ;; T
(fboundp 'bar)                          ;; T

(makunbound 'foo)
(fmakunbound 'bar)

(boundp 'foo)                           ;; nil
(fboundp 'bar)                          ;; nil

Read on to find out why I prefer the lisp approach over most other languages.

What does this look like in other languages?

In interpreted languages, like python and R there is the concept of removing or deleting a variable or function.

In python you del variable, in R you rm(variable):

foo = 5
foo

Naturally results in

Now we delete the variable:

del foo    # Delete foo
foo
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# NameError: name 'foo' is not defined

Why does common lisp appear to make it more complicated?

Why do we use makunbound and fmakunbound, etc., in common lisp?

(The following is lightly rewritten and sourced from Peter Norvig’s Paradigms of Artificial Intelligence Programming)

…because common Lisp has at least seven name spaces. The two we think of most often are

  1. Functions and Macros
  2. Variables

Python, R, Scheme, etc. conflate these two name spaces, but Common Lisp keeps them separate, so that in a function application like (f) the function/macro name space is consulted for the value of f, but in (+ f), f is treated as a variable name.

  1. Special variables form a distinct name space from lexical variables @@hugo:{{< line-break >}}@@ So the f in (+ f) is treated as either a special or lexical variable, depending on if there is an applicable special declaration.
  2. Data types @@hugo:{{< line-break >}}@@ Even if f is defined as a function and/or a variable, it can also be defined as a data type with defstruct, deftype, or defclass.
  3. Labels for go statements within a tagbody
  4. Block names for return-from statements within a block
  5. Symbols inside a quoted expression are treated as constants, and thus form name space @@hugo:{{< line-break >}}@@ These symbols are often used as keys in user-defined tables, and in a sense each such table defines a new name space. One example is the tag name space, used by catch and throw. Another is the package name space.

Just because you can do something, doesn’t mean you should

It is a good idea to limit each symbol to only one name space. Common Lisp will not be confused if a symbol is used in multiple ways, but the poor human reader probably will be.

🤣

Why do I like the lisp approach?

It cuts down on errors.

When collaborating it is easy to clash in your namespaces. You might define the variable two_pi, I might want to define two_pi as a function. My code runs, I merge it. Your code runs, you merge it. We didn’t have appropriate testing… 💣💥

Here’s a contrived example, presented in three different programming languages: R, python and (common) lisp.

Here I define a variable two_pi to be twice the value of \( π \), then I define a function to calculate the circumference of a circle and then I define a function (also) called two_pi. Let’s see what happens…

In R

two_pi <- 2 * pi

circumference <- function(r) {
  cat("R: The circumference is ", (two_pi * r), "\n")
}

# We all know pi is actually 3
two_pi <- function() {
  return(6);
}

circumference(1)

In this case R redefines what is meant by two_pi and the circumference function fails. Why? Because you need to call functions: two_pi()

Error in two_pi * r : non-numeric argument to binary operator

In python

import math

two_pi = 2 * pi

def circumference(r):
    print(f"python: The circumference is {two_pi * r}")

# We all know pi is actually 3
def two_pi():
    return 6

circumference(1)

Python also redefines what is meant by two_pi and the circumference function fails.

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in circumference
TypeError: unsupported operand type(s) for *: 'function' and 'int'

In lisp

(setf two_pi (* 2 pi))

(defun circumference (r)
  (format t "cl: The circumference is ~a~&" (* two_pi r)))

;; We all know pi is actually only 3.
(defun two_pi ()
  6)

(circumference 1)

Common lisp works, because it keeps functions and variables in difference namespaces and knows which one to call at which point.

cl: The circumference is 6.283185307179586d0

Blog Ideas [0/0]