all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: "Zhu Zihao" <all_but_last@163.com>
To: emacs-devel@gnu.org
Subject: New lisp library -- cell.el
Date: Fri, 28 Feb 2020 14:21:11 +0800 (CST)	[thread overview]
Message-ID: <b3cbce2.31e2.1708a74bc6f.Coremail.all_but_last@163.com> (raw)


[-- Attachment #1.1: Type: text/plain, Size: 1355 bytes --]

In last month I sent an email to request for some comments on weak reference facilities in Emacs. Now I create a new lisp library -- cell.el, which implemented weak reference in Elisp using weak hash table and some more util.


There are 3 kinds of cell in cell.el


- Option :: Giving "existence" information for a value, Like Maybe type in Haskell or Optional type in Java.
- Box :: Like a single element vector, create a mutable storage.
- Weak :: Weak reference I mentioned above.


IMO, these can help user to build Elisp application in more convenient way.


For a simple example.


(plist-get '(:key nil) :key) ; => nil
(plist-get '(:key nil) :val) ;=> nil


By using Option Cell, we can differentiate these two status.


(defun clear-plist-get (plist key)
  (pcase (plist-member plist key)
    (`(,_k ,v)
      (cell-option-some v))
    (_
     (cell-option-none))))

(let ((plist '(:key nil)))
  (plist-get plist :key)                ;=> nil
  (plist-get plist :val)                ;=> nil

  (clear-plist-get plist :key)          ;=> Some(nil)
  (clear-plist-get plist :val)          ;=> None
  )



I don't want to break the behaviour of plist-get, just a example to show the power of Option Cell. And there are detailed description in ;;; Commentary section.



Would you consider this to be included in Emacs core, or publish it on GNU ELPA?

[-- Attachment #1.2: Type: text/html, Size: 2152 bytes --]

[-- Attachment #2: cell.el --]
[-- Type: application/octet-stream, Size: 7322 bytes --]

;;; cell.el --- Value with extra information -*- lexical-binding: t -*-

;; Copyright (C) 2020 Zhu Zihao

;; Author: Zhu Zihao <all_but_last@163.com>
;; URL: https://github.com/cireu/emacs-cell
;; Version: 0.0.1
;; Package-Requires: ((emacs "24.3"))
;; Keywords: lisp

;; This file is NOT part of GNU Emacs.

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; For a full copy of the GNU General Public License
;; see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; * Introduction

;; This library provides a set of "Cell" for Elisp programming. A "Cell" is a
;; container of single Lisp value, with extra information attached on it.

;; Currently there are 3 kinds of "Cell" in ~cell.el~

;; ** Option

;; A Option Cell add the "existence" information for a lisp value.
;; A Option Cell may in 2 variant, ~Some~ variant means there's a value and
;; ~None~ variant means there's no value.

;; Option Cell is useful when writing funtion to query a collection. You can use
;; ~None~ variant to present "Not found". When using ~nil~ to present "Not
;;    fonud" in tranditional Lisp programming style. The caller of your query
;; function may have mistaken on "Found and get a ~nil~ value" and "Not found".

;; #+begin_src emacs-lisp
;; (defun clear-plist-get (plist key)
;;   (pcase (plist-member plist key)
;;     (`(,_k ,v)
;;       (cell-option-some v))
;;     (_
;;      (cell-option-none))))

;; (let ((plist '(:key nil)))
;;   (plist-get plist :key)                ;=> nil
;;   (plist-get plist :val)                ;=> nil

;;   (clear-plist-get plist :key)          ;=> Some(nil)
;;   (clear-plist-get plist :val)          ;=> None
;;   )
;; #+end_src

;; ~cell.el~ also provides some facility to operates on a Option Cell.

;; #+begin_src emacs-lisp
;; (let ((some (cell-option-some 1))
;;       (none (cell-option-none)))
;;   (cell-option-map some #'1+)           ;=> Some(2)
;;   (cell-option-map none #'1+)           ;=> None

;;   (cell-option-if-let (inner some)
;;       (message "Some(%d)" inner)
;;     (error "Unreachable!")))
;; #+end_src

;; ** Box

;; A Box Cell is like a single-element vector, normally used as minimal mutable
;; storage.

;; #+begin_src emacs-lisp
;; (let* ((box (cell-box-make "Emacs"))
;;        (lst (list box))
;;        (vec (vector box)))
;;   (cl-assert (eq (cell-box-inner (nth 0 lst))
;;                  (cell-box-inner (aref vec 0))))

;;   (setf (cell-box-inner (nth 0 lst)) "emacs")
;;   (cl-assert (eq (cell-box-inner (nth 0 lst))
;;                  (cell-box-inner (aref vec 0)))))
;; #+end_src


;; ** Weak

;; A Weak Cell is like weak pointer in other languages. The reference in Weak
;; Cell will not be considered in GC. This can help you manage struct with
;; cyclic reference.

;; When design a RAII style API in Emacs Lisp, Weak Cell can help you
;; differentiate "first class" reference and "second class" reference. And
;; make sure finalizer can be run as expected.

;; #+begin_src emacs-lisp
;; (let ((weakptr (cell-weak-make (list 1 2 3 4))))
;;   (cl-assert (equal (cell-weak-get weakptr)
;;                     (cell-option-some (list 1 2 3 4))))
;;   (garbage-collect)
;;   ;; value inside weakptr was GCed because nothing other than a weak cell
;;   ;; held its reference. So `cell-weak-get' will return a None.
;;   (cl-assert (equal (cell-weak-get weakptr)
;;                     (cell-option-none))))
;; #+end_src

;; * Contributions

;; Please report an issue or PR if you encounter any problem when using the library.

;;; Code:

;;; Option

(eval-when-compile (require 'cl-lib))

(cl-defstruct (cell-option
               (:constructor nil)
               (:constructor cell-option--internal-make (-val))
               (:copier nil))
  (-val nil :read-only t))

;; Constructor

(defsubst cell-option-some (value)
  "Create an option in Some variant with VALUE."
  (cell-option--internal-make value))

(defconst cell-option--none-singleton
  (let ((sym (make-symbol "rstream--none")))
    (cell-option--internal-make sym)))

(defsubst cell-option-none ()
  "Create an option in None variant."
  cell-option--none-singleton)

(defun cell-option-from-non-nil (value)
  "Create Some(VALUE) if VALUE if non-nil, otherwise create a None."
  (if value
      (cell-option-some value)
    (cell-option-none)))

;; Predicator

(defun cell-option-none-p (option)
  "Return non-nil if OPTION in None variant."
  (if (cl-typep option 'cell-option)
      (eq option (cell-option-none))
    (signal 'wrong-type-argument
            (list 'cell-option option 'option))))

(defsubst cell-option-some-p (option)
  "Return non-nil if OPTION in Some variant."
  (not (cell-option-none-p option)))

;; Operator

(cl-defmacro cell-option-if-let ((var val) then &rest else)
  "Bind the inner of VAL to VAR and yield THEN if VAL is a Some, or yield ELSE."
  (declare (indent 2) (debug ((symbolp form) form body)))
  (macroexp-let2 nil val val
    `(if (cell-option-some-p ,val)
         (let ((,var (cell-option--val ,val)))
           ,then)
       ,@else)))

(cl-defmacro cell-option-when-let ((var val) &rest forms)
  "Bind the inner of VAL to VAR and yield FORMS if VAL is a Some."
  (declare (indent 1) (debug ((symbolp form) body)))
  `(cell-option-if-let (,var ,val)
       ,(macroexp-progn forms)))

(defun cell-option-map (option func)
  "Transform the inner of OPTION using FUNC."
  (cell-option-if-let (inner option)
      (cell-option-some (funcall func inner))
    option))

(defalias 'cell-option-inner-unchecked 'cell-option--val
  "Return the inner value of OPTION directly, skip status checking.

It's your guarantee to ensure the OPTION is a Some to get meaningful result.
\n(fn OPTION)")

;;; Box

(cl-defstruct (cell-box
               (:constructor nil)
               (:constructor cell-box--make (-inner))
               (:copier nil))
  -inner)

(defalias 'cell-box-make 'cell-box--make
  "Create a single mutable unit with INITIAL-VALUE.")

(defalias 'cell-box-inner 'cell-box--inner
  "Return the inner value of BOX.
\n(fn BOX)")

;;; Weak

(cl-defstruct (cell-weak
               (:constructor nil)
               (:constructor cell-weak--internal-make)
               (:copier nil))
  (-inner-table (make-hash-table :test #'eq :weakness 'value) :read-only t))

(defun cell-weak-make (inner)
  "Create a cell takes a weak reference of INNER."
  (let* ((cell (cell-weak--internal-make))
         (internal-ht (cell-weak--inner-table cell)))
    (puthash t inner internal-ht)
    cell))

(defun cell-weak-get (cell)
  "Return Some(inner) if reference in CELL still alive, otherwise return None."
  (let* ((gensym (make-symbol "not-found"))
         (val (gethash t (cell-weak--inner-table cell) gensym))
         (found (not (eq gensym val))))
    (if found
        (cell-option-some val)
      (cell-option-none))))

(provide 'cell)

;; Local Variables:
;; coding: utf-8
;; End:

;;; cell.el ends here

             reply	other threads:[~2020-02-28  6:21 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-02-28  6:21 Zhu Zihao [this message]
2020-02-28  8:51 ` Re:New lisp library -- cell.el Zhu Zihao
2020-02-28 15:43 ` New " Stefan Monnier
2020-02-28 16:51   ` Zhu Zihao
2020-02-29  3:30     ` Stefan Monnier
2020-02-29  6:48       ` Zhu Zihao
2020-02-29 14:09         ` Stefan Monnier
2020-03-01  7:43           ` Zhu Zihao
2020-03-05 15:52             ` Stefan Monnier

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=b3cbce2.31e2.1708a74bc6f.Coremail.all_but_last@163.com \
    --to=all_but_last@163.com \
    --cc=emacs-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.