8000 GitHub - HectareaGalbis/clith: Common Lisp wITH macro.
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

HectareaGalbis/clith

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

81 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Common Lisp wITH

Welcome to Clith!

Introduction

This library defines the macro clith:with. This macro binds symbols to objects that can be finalized automatically in a personalized way.

(with ((file (open "~/file.txt" :direction :output)))
  (print "Hello Clith!" file))

clith:with is powerful enough to support almost every regular WITH- macro:

(defwith slots (vars (object) body)
  `(with-slots ,vars ,object
     ,@body))

(defstruct 3d-vector x y z)

(with ((p (make-3d-vector :x 1 :y 2 :z 3))
       ((z (up y) x) (slots p)))
  (+ x up z))
;; Returns
6

It supports declarations:

(with (((x y z) (values 1 2 3))
       ((a b c) (values 'a 'b 'c)))
  (declare (ignore a y c))
  (values x b z))
;; Returns
1
B
3

And it detects macros and symbol-macros:

(symbol-macrolet ((my-file (open "~/file.txt")))
  (with ((f my-file))
    (read f)))
;; Returns
"Hello Clith!"

Installation

  • Manual:
cd ~/common-lisp
git clone https://github.com/Hectarea1996/clith.git
  • Quicklisp:
(ql:quickload "clith")

Reference

Getting started

clith:with can be used as let or multiple-value-bind:

(with (x
       (y 3)
       ((q r) (floor 4 5)))
  (values x y q r))
;; Returns
NIL
3
0
4

The macro clith:with uses WITH expansions in a similar way to setf. These expansions control how the macro clith:with is expanded.

(let (some-stream)

  (with ((the-stream (open "~/test.txt")))
    (setf some-stream the-stream)
    (format t "Stream opened? ~s~%" (open-stream-p some-stream)))

  (format t "Stream opened after? ~s" (open-stream-p some-stream)))
;; Output
Stream opened? T
Stream opened after? NIL
;; Returns
NIL

Every Common Lisp function that creates an object that should be closed/destroyed has a WITH expansion defined by CLITH. For example, functions like open or make-two-way-stream have a WITH expansion. See all the functions in the reference.

Also, we can check if a symbol denotes a WITH expansion using clith:withp:

(withp 'open)
;; Returns
T

Defining a WITH expansion

Simple example: MAKE-WINDOW

In order to extend the macro clith:with we need to define a WITH expansion. To do so, we use clith:defwith.

Suppose we have (MAKE-WINDOW TITLE) and (DESTROY-WINDOW WINDOW). We want to control the expansion of WITH in order to use both functions. Let's define the WITH expansion:

(defwith make-window ((window) (title) body)
  "Makes a window that will be destroyed after the end of WITH."
  (let ((window-var (gensym)))
    `(let ((,window-var (make-window ,title)))
       (unwind-protect
           (let ((,window ,window-var))
             ,@body)
         (destroy-window ,window-var)))))
;; Returns
MAKE-WINDOW

This is a common implementation of a WITH- macro. Note that we specified (window) to specify that only one variable is wanted.

Now we can use our expansion:

(with ((my-window (make-window "My window")))
  ;; Doing things with the window
  )

After the evaluation of the body, my-window will be destroyed by destroy-window.

No need to return a value: INIT-SUBSYSTEM

There are WITH- macros that doesn't return anything. They just initialize something that should be finalized at the end. Imagine that we have the functions INIT-SUBSYSTEM and FINALIZE-SUBSYSTEM. Let's define a WITH expansion that calls to FINALIZE-SUBSYSTEM:

(defwith init-subsystem (() () body) ; <- No variables to bind and no arguments.
  "Initialize the subsystem and finalize it at the end of WITH."
  `(progn
     (init-subsystem)
     (unwind-protect
         (progn ,@body)
       (finalize-subsystem))))

Now we don't need to worry about finalizing the subsystem:

(with (((init-subsystem)))
  ...)

Extended syntax: GENSYMS

Some WITH- macros like with-slots allow to specify some options to variables. Let's try to make a WITH expansion that works like alexandria:with-gensyms. Each variable should optionally accept the prefix for the fresh generated symbol.

We want to achieve something like this:

(with ((sym1 (gensyms))                  ; <- Regular syntax
       ((sym2 (sym3 "FOO")) (gensyms)))  ; <- Extended syntax for SYM3
  ...)

In order to do this, we are using gensym:

(defwith gensyms (vars () body)
  (let* ((list-vars (mapcar #'alexandria:ensure-list vars))
         (sym-vars (mapcar #'car list-vars))
         (prefixes (mapcar #'cdr list-vars))
         (let-bindings (mapcar (lambda (sym-var prefix)
                                 `(,sym-var (gensym ,(if prefix (car prefix) (symbol-name sym-var)))))
                               sym-vars prefixes)))
    `(let ,let-bindings
       ,@body)))
;; Returns
GENSYMS

Each element in VARS can be a symbol or a list. That's the reason we are using alexandria:ensure-list. LIST-VARS will contain lists where the first element is the symbol to bound and can have a second element, the prefix. We store then the symbols in SYM-VARS and the prefixes in PREFIXES. Note that if a prefix is not specified, then the corresponding element in PREFIXES will be NIL. If some PREFIX is NIL, we use the name of the respective SYM-VAR. Finally, we create the LET-BINDING and use it in the final form.

Let's try it out:

(with ((x (gensyms))
       ((y z) (gensyms))
       (((a "CUSTOM-A") (b "CUSTOM-B") c) (gensyms)))
  (values
74EC
 (list x y z a b c)))
;; Returns
(#:X329 #:Y330 #:Z331 #:CUSTOM-A332 #:CUSTOM-B333 #:C334)

Documentation

The macro clith:defwith accepts a docstring that can be retrieved with the function documentation. Check out again the definition of the expansion of make-window above. Note that we wrote a docstring.

(documentation 'make-window 'with)
;; Returns
"Makes a window that will be destroyed after the end of WITH."

We can also setf the docstring:

(setf (documentation 'make-window 'with) "Another docstring!")
(documentation 'make-window 'with)
;; Returns
"Another docstring!"

Declarations

The macro clith:with accepts declarations. These declarations are moved to the correct place at expansion time. For example, imagine we want to open two windows, but the variables can be ignored:

(with ((w1 (make-window "Window 1"))
       (w2 (make-window "Window 2")))
  (declare (ignorable w1 w2))
  (print "Hello world!"))

Let's see the expanded code:

(macroexpand-1 '(with ((w1 (make-window "Window 1"))
                       (w2 (make-window "Window 2")))
                  (declare (ignorable w1 w2))
                  (print "Hello world!")))
;; Returns
(LET ((#:G346 (MAKE-WINDOW "Window 1")))
  (UNWIND-PROTECT
      (LET ((W1 #:G346))
        (DECLARE (IGNORABLE W1))
        (LET ((#:G343 (MAKE-WINDOW "Window 2")))
          (UNWIND-PROTECT
              (LET ((W2 #:G343))
                (DECLARE (IGNORABLE W2))
                (PRINT "Hello world!"))
            (DESTROY-WINDOW #:G343))))
    (DESTROY-WINDOW #:G346)))
T

Observe that the declarations are in the right place. Every symbol that can be bound is a candidate for a declaration. If more that one candidate is found (same symbol appearing more than once) the last one is selected.

About

Common Lisp wITH macro.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
0