close

#CommonLisp #Lisp

(set-macro-character #\[
  (lambda (stream char)
    (declare (ignore char))
    (read-delimited-list #\] stream t)))

(set-macro-character #\]
  (lambda (stream char)
    (declare (ignore stream char))
    (error "unmatched bracket on input")))
CL-USER> [defun frobnicate [foo bar]
           [list [+ foo bar] [- foo bar]
                 [* foo bar] [/ foo bar]]]
FROBNICATE
CL-USER> [frobnicate 42 20]
(62 22 840 21/10)

#CommonLisp #Lisp #Alexandria

Someone asked a question on #lisp on Freenode: since WHEN-LET has an analogous WHEN-LET*, why IF-LET has no analogous IF-LET*?

Here is my attempt at answering it.


Suppose we have a following code block:

(if-let ((x 42) 
         (y nil)
         (z :foo))
  (list :true x y z)
  (list :false x y z))

All of X, Y, and Z are bound to the respective values: 42, NIL, and :FOO. At least one of the values is false. Therefore, it should be obvious that the result of evaluating this code would be the list (:FALSE 42 NIL :FOO).

Now, let us slightly modify this code:

(if-let* ((x 42) 
          (y nil)
          (z :foo))
  (list :true x y z)
  (list :false x y z))

We no longer use IF-LET but instead we use the hypothetical sequential version, IF-LET*. We evaluate X and Y. By definition, we are supposed to not evaluate the following variables at this moment, since Y evaluated to false, and we instead execute the false branch.

The big question is: what is the value of Z in the false branch?

It is certainly not :FOO because we have not managed to evaluate that binding yet. In fact, that lexical variable should be unbound since there is no logical value for it, but there is no such thing as an unbound lexical variable in Lisp. In particular, this might mean that we encounter any number of logically unbound variables in the false branch. We could work around it by setting the not-yet-bound lexical variables to some predefined value (which would greatly complicate the code and introduce a lot of mess) or we would make the false branch unable to use any of the lexical bindings, which makes the sequential IF-LET* greatly inconsistent with the parallel IF-LET.


In other words: having IF-LET* would be inconsistent. None of the variables are available in the lexical scope of the false branch, and that should be part of IF-LET*'s contract.

  • favorite

    1 Like

    Claes Wallin (韋嘉誠)

  • person chevron_right

    VALIDATE-SUPERCLASS explained

    Michał "phoe" Herda – Tuesday, 25 December, 2018 - 16:42 edit

#CommonLisp #Lisp #MOP

(This blogpost is also known as "WTF is VALIDATE-SUPERCLASS and why do I have to define methods on it to get a metaclass to work?".)


Let us assume that we have a set of superclasses. In this case, let us define two of them, but the number does not matter: can be zero, can be one, can be five.

Additionally, let us define a new subclass of STANDARD-CLASS, that we will call our metaclass.

(defclass superclass-1 () ())
(defclass superclass-2 () ())
(defclass metaclass (standard-class) ())

Let us attempt to create a subclass of these two superclasses, while also using our newly created metaclass.

(defclass subclass (superclass-1 superclass-2) ()
  (:metaclass metaclass))

This signals an error.

The class #<STANDARD-CLASS SUPERCLASS-1> was specified as a superclass of 
the class #<METACLASS SUBCLASS {100377A0D3}>, but the metaclasses
#<STANDARD-CLASS STANDARD-CLASS> and #<STANDARD-CLASS METACLASS> are
incompatible.

There are four distinct classes mentioned in the error message. First of all, there is one of our superclasses, SUPERCLASS-1, then, there is the class object named SUBCLASS that we are trying to define. Then, there are two more class objects, STANDARD-CLASS and METACLASS.

The magical combination of parentheses that causes the above DEFCLASS form to evaluate successfully is this mysteriously-looking method:

(defmethod validate-superclass
    ((class metaclass) (superclass standard-class))
  t)

Let us analyze why this particular method fixes our problem and allows us to define classes with the metaclass of our choosing.

The MOP description of VALIDATE-SUPERCLASS says that this function is called with two arguments: class, and superclass, to determine whether the class superclass is suitable for use as a superclass of class.

In other words, if Common Lisp Object System is supposed to permit a situation when superclass becomes a superclass of class, this function needs to return true. If such a situation is forbidden, this function must return false.

The MOP says that the default system-defined method on VALIDATE-SUPERCLASS returns true only in three cases:

  1. if the superclass argument is the class named T,
  2. if the class of the class argument is the same as the class of the superclass argument, or
  3. if the class of one of the arguments is STANDARD-CLASS and the class of the other is FUNCALLABLE-STANDARD-CLASS.

The first case is trivial; every class is a subclass of the class T, therefore it would be pointless to return false on

The third case is there to assure that standard objects and funcallable standard objects can interoperate. A subclass of a funcallable class does not have to be funcallable, and the other way around, too: a subclass of a non-funcallable class may be funcallable.

The second case is what allows the basics of CLOS class definitions to work. Whenever we define a new class with DEFCLASS without any superclasses, we define a new instance of STANDARD-CLASS that has STANDARD-OBJECT as its superclass. STANDARD-OBJECT is an instance of STANDARD-CLASS; the newly defined class is an instance of STANDARD-CLASS; therefore, VALIDATE-SUPECLASS returns true. Whenever we define a new class with any number of superclasses that itself are also instances of STANDARD-CLASS, the same thing happens: each of the superclasses is an instance of STANDARD-CLASS, just like the class we are defining right now, which allows VALIDATE-SUPERCLASS to return true in each single case.

The issue happens when we specify the :METACLASS option to DEFCLASS. :METACLASS states that the new class should be an instance of the provided class. If we pass the option (:METACLASS FOO-CLASS), then the newly instantiated class metaobject will be an instance of FOO-CLASS.

But, remember that we also need to specify superclasses for that class! Unless we explicitly specify a superclass, then it is going to default to STANDARD-OBJECT, in which case, VALIDATE-SUPERCLASS will be called on a pair of arguments: the class we are attempting to create, an instance of FOO-CLASS, and the class STANDARD-OBJECT, an instance of STANDARD-CLASS, which is stated to be its superclass.

Neither of the three cases the default method fit this combination, therefore the method returns NIL. The validation fails, and an error is signaled from the DEFCLASS macro.

This is why we explicitly need to state that we, the programmers, want class STANDARD-OBJECT to be a valid superclass for instances of FOO-CLASS. This is the purpose of the VALIDATE-SUPERCLASS method defined on FOO-CLASS as the class we want to instantiate and STANDARD-OBJECT as the superclass we want to assign to the class.

In other words, the way the aforementioned MOP usage works this way is because VALIDATE-SUPERCLASS serves a greater function than mere playing with metaclasses - it is the central MOP mechanism for declaring which combinations of classes and subclasses are allowed in the Lisp image. Methods on that function which may be specialized in any way, including methods that use EQL-specializers and perform arbitrary computation.

For an example of the above, let us state that the class named FOO may be subclassed only by classes whose name contain the letter E.

(defmethod sb-mop:validate-superclass
    ((class class) (superclass (eql (find-class 'foo))))
  (find-if (lambda (x) (member x '(#\E #\e)))
           (symbol-name (class-name class))))

The above snippet denies (defclass bar (foo) ()), but permits (defclass fred (foo) ()).

It is also possible to imagine a class hierarchy where a programmer decides, for any reason, that a class named BAR should not be allowed to be subclassed at all. The following snippet is enough to achieve that:

(defmethod sb-mop:validate-superclass
    ((class class) (superclass (eql (find-class 'bar))))
  nil)

(To add to the confusion, the error message we get from SBCL in such a case is not very helpful, as it states, the metaclasses #1=#<STANDARD-CLASS COMMON-LISP:STANDARD-CLASS> and #1# are incompatible. I have raised a bugticket on that edge case.)


Summing up, VALIDATE-SUPERCLASS is a general mechanism for describing which kinds of superclass-subclass relations we want to see in the Lisp image. Practice has shown that this function is particularly often used in context of permitting instances of non-STANDARD-CLASSes to inherit from instances of STANDARD-CLASSes - in other words, allowing metaclasses to be instantiated. The way it is done is via defining methods on this function, specialized on the metaclass we want to instantiate, and the superclass that we want to assign to the instances of that metaclass - either a particular superclass of our choosing, or STANDARD-OBJECT as the default. These methods must return true whenever we want to permit a particular combination to happen.

  • person chevron_right

    Typing parentheses the Lisp Machine way

    Michał "phoe" Herda – Sunday, 23 December, 2018 - 12:03

(This is a repost of an old blog post of mine from Teknik.)

#lisp #linux

setxkbmap -option parens:swap_brackets

Running this command swaps typing the () and [] in your X session. Upsides: you no longer need to press Shift to type parens. Downsides: getting used to it.

Via Reddit. Putting this here so I don't forget.

(This is a repost of an old blog post of mine from Teknik.)

#common-lisp

I had a function that returned multiple values.

I wanted to iterate over it while collecting all of the values, so I avoid losing any data.

And I found out that, welp, MAPCAR only works on primary return values.

And so, with some help from #lisp on Freenode and Bike doing an actual majority of the coding work, MULTIPLE-VALUE-MAPCAR was born.

(defun multiple-value-mapcar (function &rest lists)
  (assert (not (null lists)))
  (let* ((values (loop for l = lists then (mapcar #'cdr l)
                       until (some #'endp l)
                       collecting (multiple-value-list
                                   (apply function (mapcar #'car l)))))
         (max-values (loop for vl in values maximizing (length vl)))
         (lists (make-list max-values)))
    (loop for vl in values
          do (loop for i from 0 below max-values
                   do (push (nth i vl) (nth i lists))))
    (values-list (mapcar #'nreverse lists))))

(multiple-value-mapcar #'values 
                       '(1 2 3 4) 
                       '(a b c d e f) 
                       '(:q :w :e :r :t)) 
;;=> (1 2 3 4)
;;=> (A B C D)
;;=> (:Q :W :E :R)

(let ((hash (make-hash-table))) 
  (setf (gethash 'fuck hash) t 
        (gethash 'shit hash) nil)
  (multiple-value-mapcar (rcurry #'gethash hash) 
                         '(fuck shit stack))) 
;;=> (T NIL NIL)
;;=> (T T NIL)

(This is a repost of an old blog post of mine from Teknik.)

#alexandria #common-lisp

I had a hash table in a running Common Lisp image that I wanted to print readably while preserving object identity (mostly to avoid data duplication).

Alexandria and Bike from #lisp came to the rescue.

;;; (ql:quickload :alexandria)

(defun print-hash-table-readably (hash-table
                                  &optional (stream *standard-output*))
  "Prints a hash table readably using ALEXANDRIA:ALIST-HASH-TABLE."
  (let ((test (hash-table-test hash-table))
        (*print-circle* t)
        (*print-readably* t))
    (format stream "#.(ALEXANDRIA:ALIST-HASH-TABLE '(~%")
    (maphash (lambda (k v) (format stream "   (~S . ~S)~%" k v)) hash-table)
    (format stream "   ) :TEST '~A)" test)
    hash-table))
  • person chevron_right

    Answers from 2017 Common Lisp experts - Community

    Michał "phoe" Herda – Sunday, 23 December, 2018 - 11:40

(This is a repost of an old blog post of mine from Teknik. This blog post was split into multiple parts to accomodate for Movim's size limitations; use hypelinks in the table of contents to navigate.)


Why are there so few instructional or advocacy bloggers?

Aidenn0

I think it's because it's a smaller and older community. Also the community is very diverse for its small size.

phoe

Oh come on. Paul Graham is enough when it comes to advocates. :)

I think it directly has to do with the size of the community. Nonetheless, Planet Lisp has a rather large list of blogs that it scraps for articles. You might want to take a look at it.

Hexstream

The best kind of advocacy is making things better, which I'm already trying to do.


Why can’t Google search find many good resources? I.e. a search for “Common Lisp” is not that helpful.

phoe

Size of the community, again. I think one of the best places for finding resources is actually going to talk with people who use the language, say, {#lisp,#lispgames,#lispweb} @ Freenode or the Lisp Discord server and ask them for advice. The language is old, websites tend to fade along with the users and become stale and not updated.

Hopefully the UltraSpec (oh gods when is he going to stop talking about it) will alleviate this.

edgar-rft

Beause there are very little good resources of anything at all in the internet. Most other things are much shittier than Common Lisp, so nobody there notices. That's the only difference. It's a perceptional illusion.

  • person chevron_right

    Answers from 2017 Common Lisp experts - Table of Contents

    Michał "phoe" Herda – Sunday, 23 December, 2018 - 11:30 edit

(This is a repost of an old blog post of mine from Teknik. This blog post was split into multiple parts to accomodate for Movim's size limitations; use hypelinks in the table of contents to navigate.)

This post is a compilation of answers to the questions posted in a blog post titled Questions for 2017 Common Lisp experts, posted by anticrisis.

I have decided that the questions asked in that post are good enough to deserve a response post that gathers the collective knowledge of the Lisp community in a FAQ of sorts.

It's somewhat baffling to say that "experts", whoever they are, have gathered to respond to this question - I simply assume that the people who have voiced their opinion on the topic - regardless of who they are - know what they are talking of, and therefore their comments should be, and have been, included here.

This post includes answers shamelessly scavenged from the reddit thread discussing these comments along with answers that I was able to collect from other places and people - to be precise #lisp @ Freenode and the Lisp Discord server.

The responses were slightly edited where necessary to add context, punctuation and capitals; I am to blame for all unintended changes to meaning that it might have caused. On the other hand, even if the answers were mostly duplicating each other, I have decided to keep them all; I do not want to editorialize here, but instead try to (perhaps a bit experimentally) show the inner workings of the Lisp community's hivemind and outline the questions that are perhaps the most controversial and eliciting mixed views and responses among the community. (Why? Because I can.)

This post also includes my own answers mixed in between all others.


Libraries

  • Why don’t libraries have version numbers?
  • Why is adding libraries to Quicklisp a manual process that requires human review? Is there an alternative non-curated registry?
  • Why aren’t ASDF dependencies automatically fetched if missing?
  • Why do so few libraries use GitHub?

Open source

  • Are there serious open source efforts to modernize documentation?
  • Are there serious open source efforts to modernize libraries?
  • Are there efforts to encourage open source contributions?
  • My apologies, but I’d never even heard of GitLab prior to seeing all these libraries hosted on common-lisp.net. Why not GitHub?

Compilers

  • Why hasn’t the open source community standardized on a single implementation, i.e. SBCL?
  • Would there be any benefit to extending SBCL beyond the Common Lisp standard to better support modern language features and paradigms?

Functional programming

  • Why aren’t there more libraries using a functional, immutable paradigm? Most seem to use many dynamic variables and large functions spanning multiple screens, with variables mutating throughout
  • Are there any functional programming advocates in the Common Lisp open source community?

Innovation

  • Are there any efforts to bring innovative libraries from the Clojure (or elsewhere) community into Common Lisp?
  • Is there software design innovation occurring in the Common Lisp community or is the community primarily focused on maintenance and tuning?
  • Is it worth the time and energy to develop new libraries to modernize Common Lisp – or perhaps a single extended implementation of it, like SBCL – or would that time and energy be more productively spent in a non-Common Lisp community?

Onboarding

  • Why is on-boarding new developers so difficult? For instance, why isn’t Roswell linked to from every About Common Lisp page?
  • Why hasn’t the Common Lisp Foundation updated the common-lisp.net home page since 2015?

Community

  • Why are there so few instructional or advocacy bloggers?
  • Why can’t Google search find many good resources? I.e. a search for “Common Lisp” is not that helpful.

More links for newbies:

If you'd like your own answers to be included here, give me a hint on email.

The comments have been scraped and posted here in good will; they belong to their authors and I'll remove any of them the moment I hear from the author that they should be removed.