From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Michael Heerdegen Newsgroups: gmane.emacs.devel Subject: Re: A generalization of `thunk-let' Date: Fri, 12 Jan 2018 21:03:32 +0100 Message-ID: <87k1wmetez.fsf@web.de> References: <87infp9z6j.fsf@web.de> <87r2s5ez0t.fsf@web.de> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: blaine.gmane.org 1515787370 6626 195.159.176.226 (12 Jan 2018 20:02:50 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Fri, 12 Jan 2018 20:02:50 +0000 (UTC) User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.0.50 (gnu/linux) Cc: emacs-devel@gnu.org To: Stefan Monnier Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Fri Jan 12 21:02:46 2018 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ea5X8-0000Ud-DU for ged-emacs-devel@m.gmane.org; Fri, 12 Jan 2018 21:02:34 +0100 Original-Received: from localhost ([::1]:35300 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ea5Z6-0005rP-Ii for ged-emacs-devel@m.gmane.org; Fri, 12 Jan 2018 15:04:36 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:46008) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ea5YF-0005q0-NQ for emacs-devel@gnu.org; Fri, 12 Jan 2018 15:03:44 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ea5YB-0008BU-QJ for emacs-devel@gnu.org; Fri, 12 Jan 2018 15:03:43 -0500 Original-Received: from mout.web.de ([212.227.17.11]:63523) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1ea5YB-00089x-EQ for emacs-devel@gnu.org; Fri, 12 Jan 2018 15:03:39 -0500 Original-Received: from drachen.dragon ([92.74.168.127]) by smtp.web.de (mrweb101 [213.165.67.124]) with ESMTPSA (Nemesis) id 0MSaZk-1eSUKX00tI-00RWp8; Fri, 12 Jan 2018 21:03:33 +0100 In-Reply-To: (Stefan Monnier's message of "Fri, 08 Dec 2017 16:16:48 -0500") X-Provags-ID: V03:K0:+ucO9XBeHZLe7PzHGoAJ0JnQHEQbCEWepXDoAX+9aN/sG7knwH4 Cu3AFlyU/+CgKK53Mx3NvEx+1JuFOMCX6ZG4rJYOlLR7e7yEtBnifg0HufOoe3uNnAk1Hc7 6liV3B3EASFmAsyxlKCxkOvho9EGWtF770BEwS+39jYvSMAL6o4MU6TnKqZpb7n66D6zr2m FqYFJ4FYhn/UX9vq/dwxA== X-UI-Out-Filterresults: notjunk:1;V01:K0:FqxYCpncZqA=:FqGq1w+Lp+3V9RPFFhvojl 9Y0sQmZu2CctFl6/+a7s+CaCmx1J3P1YYIBhQBY/OffPTyjyr3/WwvlxdFYEmpVQlgMohHDVk nXnlX3/keodrXVrwQQbdr2veX5Fl3jtZunGCqThUOw2v/gwf5+pp5/73VqpSoGZOJB/NtVaSK BXVVUtJYi8JILzW0aDhchrBn4gqXe26CEIKr6vsd2kuaBraxq/jp4+CF9b9Vw/8vRF/pVI9km NudJXoDJCFWc9BWMoXFwUkFxrvIYaqRs/NhuxaHfCXFWKcZ9V7bHufhqsbfa+etTmtovYMSB+ YTxMy2ld3e6Hib3/z5PTfWMpVNTonBoF1wGddX4YvXUwwBcQO8wKQ4Mf56XdXme8FL5smgP+f LswAURDftZ/Qx6Fv3C53TRKy3I/pv2zjEWXdfdKHCfSQsWFpHtbr2aMjNTO2CeeHifopzl58l lnBLRRLx/XBIraFO/JK/xXFuRQxi6XiqS2uHDx1YkA8XR11anSxx+dAaxM4tcjXYLg+KGlPTB NFTKeGS6H9JtMdgWGXqzaxmuUOdpSpNmL+MIfOFJIF1kHueUTT3lHU6BkRDHyoenawJfc1CxT BuFmMNJ8vgpCJ1EsveU3Ar3NYq449ov5UAAOPWsZAUjP92TlGGoPv7pFdW0kC56Cxh7PwVApB 8+PaYjJAasPIeOcvq2uqjcekB9ihu7gCuzrR0YPnoCFGgDSafXmU6aKj/bTWyZDffTPY3CEGZ FrOcTBVoAeuUTCy3m5etjl2ayNQ2oKG8cyo7HVBcMl7vlQRKLuCpIkKwXgA17GwAI4iFd9/F X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 212.227.17.11 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:221902 Archived-At: --=-=-= Content-Type: text/plain Stefan Monnier writes: > Another way to do that is to create a new kind of object which is like > a spreadsheet-cell: it comes with an expression to compute it, and > remembers its current value. Internally it also keeps track of the > other spreadsheet-cells used during the computation of the current > value (i.e. the dependencies are automatically tracked for you). > > You also get to choose whether to check the current value's validity > lazily (like you do in your code), or to eagerly mark dependencies > "dirty" when a cell is modified. I think I've got that working - see attachment (though I chose different function names). Of course we want a gv-setter for `sscell-get'! A surprising side effect of this is that, contrary to thunk-let, sscell-let bound variables suddenly get settable, e.g. #+begin_src emacs-lisp (sscell-let ((x () 1)) (let ((y 7)) (setq x (+ x y)) (* 10 x))) ==> 80 #+end_src Michael. --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=sscell.el Content-Transfer-Encoding: quoted-printable ;;; sscell.el --- An implementation of abstract spreadsheet cell objects = -*- lexical-binding: t -*- ;; Copyright (C) 2018 Free Software Foundation, Inc ;; Author: Michael Heerdegen ;; Maintainer: Michael Heerdegen ;; Created: 2017_12_11 ;; Keywords: lisp ;; Version: 0.1 ;; Package-Requires: ((emacs "25")) ;; This file is not part of GNU Emacs. ;; GNU Emacs 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 of the License, or ;; (at your option) any later version. ;; GNU Emacs 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. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; This package implements objects that are an abstract version of ;; spreadsheet cells. Note that this has nothing to do with a ;; spreadsheet application (though you could use sscells to implement ;; one, but this is not the goal of this package) - these sscells are ;; a data type useful for general Elisp programming. ;; ;; An sscell is an object containing a value field, a calculation rule ;; to update that cell's value, and a set of dependencies. There are ;; two types of dependencies: implicit and static dependecies. Static ;; dependencies are specified when creating an sscell: ;; ;; (sscell-make static-deps rule) ;; ;; where RULE is the calculation rule for the returned cell. You ask ;; an sscell for its value with (sscell-get S). The value is ;; calculated when it has not been calculated yet, or when one of the ;; dependencies changed. ;; ;; Static dependencies are expressions that are evaluated to check ;; whether the saved value is still valid. Whenever one of these ;; expressions evaluates to a value different from the last time, the ;; sscell counts as invalid, and any call to `sscell-get' will trigger ;; a recomputation of the cell value. "Different value" means not ;; `eq' by default, but you can also specify a different test ;; predicate. ;; ;; Whenever the calculation of the value of an sscell refers to a ;; value of another sscell, the value of that other sscell is ;; remembered as an implicit dependency. Whenever the cell value of ;; that second cell changes, the first cell counts as invalid. ;; ;; You can change the computation expression of an sscell with ;; `sscell-set', and also set the value directly with ;; `sscell-set-value' (which nullifies all dependencies and gives you ;; something like an "input cell"). ;; ;; This package also implements let-like binding constructs ;; `sscell-let' and `sscell-let*'. These constructs create lazy ;; bindings using sscells implicitly. The created bindings are ;; silently recomputed when referenced and any declared dependencies ;; changed. ;; ;; Examples: ... ;;; Code: (require 'cl-lib) (defvar sscell--tag (make-symbol "sscell")) (defvar sscell--new-dynamic-deps nil) (defvar sscell--asking-sscell nil) (defun sscellp (object) "Return non-nil when the OBJECT is an sscell." (eq (car-safe object) sscell--tag)) (defmacro sscell-make--1 (static-deps rule &optional reuse-cons) ;; Like `sscell-make', but with an additional optional arg REUSE-CONS: ;; When specified, it must be a cons cell C with car sscell--tag, and ;; the return value is the manipulated cons C. (declare (indent 1)) (cl-callf or static-deps '(t)) (let ((last-result (make-symbol "last-result")) (last-static-dep-results (make-symbol "last-dep-results")) (new-dep-results (make-symbol "new-dep-results")) (instruction (make-symbol "instruction")) (static-tests (cl-maplist (lambda (more-deps) (let ((dep (car more-deps))) (if (not (eq (car-safe dep) :test)) '#'eq (prog1 (nth 1 dep) (setcar more-deps (nth 2 dep)))= ))) static-deps)) (dynamic-deps (make-symbol "dynamic-deps")) (last-dynamic-dep-results (make-symbol "last-dynamic-dep-results")) (self (make-symbol "self")) (cell-invalid-p (make-symbol "cell-invalid-p")) (get-value (make-symbol "get-value")) (arg (make-symbol "arg"))) `(let ((,get-value (lambda () ,rule)) ,last-result (,last-static-dep-results nil) (,dynamic-deps nil) (,last-dynamic-dep-results nil)) (let ((,cell-invalid-p (lambda () (let ((,new-dep-results (list ,@static-deps))) (unless (and ,last-static-dep-results (cl-every #'identity (cl-mapcar #'funcall (list ,@stati= c-tests) ,last-static-dep-result= s ,new-dep-results)) (cl-every #'identity (cl-mapcar #'eq ,last-dynamic-dep-resul= ts (mapcar #'sscell-get ,d= ynamic-deps)))) ,new-dep-results))))) (let ((,self (or ,reuse-cons (cons sscell--tag nil)))) (setcdr ,self (lambda (,instruction &optional ,arg) (pcase-exhaustive ,instruction (:valid? (if (not (funcall ,cell-invalid-p)) t (setq ,last-static-dep-results nil) nil)) (:get (when sscell--asking-sscell (add-to-list 'sscell--new-dynamic-deps ,self)) (let ((sscell--asking-sscell ,self) (sscell--new-dynamic-deps nil)) (when-let ((,new-dep-results (funcall ,cell-inval= id-p))) (setq ,last-static-dep-results ,new-dep-results ,last-result (funcall ,get-v= alue) ,dynamic-deps sscell--new-dyn= amic-deps ,last-dynamic-dep-results (mapcar #'sscel= l-get ,dynamic-deps)))) ,last-result)))) ,self))))) (defmacro sscell-make (static-deps rule) "Make an sscell. STATIC-DEPS is a list of the static dependencies of the sscell. A static dependency is either: EXPR or (:test TESTFUN EXPR) RULE is an expression to (re-)calculate the cell value." (declare (indent 1)) `(sscell-make--1 ,static-deps ,rule nil)) (defun sscell-get (sscell) (cl-assert (sscellp sscell)) (funcall (cdr sscell) :get)) (defun sscell-valid-p (sscell) (cl-assert (sscellp sscell)) (funcall (cdr sscell) :valid?)) (defmacro sscell-set (sscell new-static-deps new-rule) `(sscell-make--1 ,new-static-deps ,new-rule ,sscell)) (defun sscell-set-value (sscell value) (sscell-set sscell () value) value) (gv-define-simple-setter sscell-get sscell-set-value) (defmacro sscell-let (bindings &rest body) (declare (indent 1) (debug fixme)) (cl-callf2 mapcar (pcase-lambda (`(,var ,deps ,binding)) (list (make-symbol (concat (symbol-name var) "-sscell")) var deps binding)) bindings) `(let ,(mapcar (pcase-lambda (`(,helper-var ,_var ,deps ,binding)) `(,helper-var (sscell-make ,deps ,binding))) bindings) (cl-symbol-macrolet ,(mapcar (pcase-lambda (`(,helper-var ,var ,_deps ,_binding)) `(,var (sscell-get ,helper-var))) bindings) ,@body))) (defmacro sscell-let* (bindings &rest body) (declare (indent 1) (debug fixme)) (cl-reduce (lambda (expr binding) `(sscell-let (,binding) ,expr)) (nreverse bindings) :initial-value (macroexp-progn body))) (provide 'sscell) ;;; sscell.el ends here --=-=-=--