summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2021-10-26 02:33:01 -0700
committerKaz Kylheku <kaz@kylheku.com>2021-10-26 02:33:01 -0700
commit30638a91a2c47acd08cf67735eac31e5adcf4eba (patch)
treebac6ac5c1f3cd4eba8fb2c52c0ef9a01f1649e28
parent02a73c7785f5cdd7975cf10f83b430dfcc6778a3 (diff)
downloadtxr-30638a91a2c47acd08cf67735eac31e5adcf4eba.tar.gz
txr-30638a91a2c47acd08cf67735eac31e5adcf4eba.tar.bz2
txr-30638a91a2c47acd08cf67735eac31e5adcf4eba.zip
places: new accessor read-once.
* lisplib.c (place_set_entries): Trigger autoload on read-once. * stdlib/place.t (read-once): New function and place. * txr.1: Documented. * stdlib/doc-syms.tl: Updated.
-rw-r--r--lisplib.c4
-rw-r--r--stdlib/doc-syms.tl1
-rw-r--r--stdlib/place.tl11
-rw-r--r--txr.1170
4 files changed, 184 insertions, 2 deletions
diff --git a/lisplib.c b/lisplib.c
index 5e2ff629..7e6dd39f 100644
--- a/lisplib.c
+++ b/lisplib.c
@@ -98,8 +98,8 @@ static val place_set_entries(val dlt, val fun)
lit("test-inc"), lit("test-dec"),
lit("pushnew"), lit("del"), lit("lset"), lit("upd"),
lit("defplace"), lit("define-place-macro"), lit("define-modify-macro"),
- lit("placelet"), lit("placelet*"), lit("define-accessor"),
- lit("with-slots"),
+ lit("placelet"), lit("placelet*"), lit("read-once"),
+ lit("define-accessor"), lit("with-slots"),
nil
};
diff --git a/stdlib/doc-syms.tl b/stdlib/doc-syms.tl
index 38f5dc7b..7df26d2e 100644
--- a/stdlib/doc-syms.tl
+++ b/stdlib/doc-syms.tl
@@ -1544,6 +1544,7 @@
("rcomb" "N-02D9003C")
("rcons" "N-02E9003D")
("read" "N-03FE5500")
+ ("read-once" "N-010F1250")
("read-until-match" "N-001D3F81")
("readdir" "N-0289D074")
("readlink" "N-0338B219")
diff --git a/stdlib/place.tl b/stdlib/place.tl
index 1eb42b56..a08e320a 100644
--- a/stdlib/place.tl
+++ b/stdlib/place.tl
@@ -862,6 +862,17 @@
^(macrolet ((,ssetter (val) ^(slotset ,',struct ,',sym ,val)))
,body)))
+(defun read-once (value) value)
+
+(defplace (read-once place) body
+ (getter setter
+ (with-gensyms (cache-var pgetter psetter)
+ (with-update-expander (pgetter psetter) place sys:*pl-env*
+ ^(slet ((,cache-var (,pgetter)))
+ (macrolet ((,getter () ',cache-var)
+ (,setter (val) ^(,',psetter (set ,',cache-var ,val))))
+ ,body))))))
+
(defmacro define-modify-macro (name lambda-list function)
(let ((cleaned-lambda-list (mapcar [iffi consp car]
(remql : lambda-list))))
diff --git a/txr.1 b/txr.1
index 5df87f49..66df2a59 100644
--- a/txr.1
+++ b/txr.1
@@ -13853,6 +13853,7 @@ defined by \*(TX programs.
.mets (left << node )
.mets (right << node )
.mets (key << node )
+.mets (read-once << node )
.onom
.NP* Built-In Place-Mutating Operators
@@ -38611,6 +38612,26 @@ Rather it may be substituted by one kind of form when it
is treated as a pure value, and another kind of form
when it is treated as a place.
+Note: multiple accesses to an alias created by
+.code placelet
+denote multiple accesses to the aliased storage location.
+That can mean multiple function calls or array indexing operations and such.
+If the target of the alias is
+.mono
+.meti (read-once << place )
+.onom
+instead of
+.metn place ,
+then a single access occurs to fetch the prior value of
+.meta place
+and stored into a hidden variable. All of the multiple occurrences of the
+alias then simply retrieve this cached prior value from the hidden
+variable, rather than accessing the place. The
+.code read-once
+macro is independent of
+.code placelet
+and separately documented.
+
.TP* "Example:"
Implementation of
@@ -41670,6 +41691,155 @@ Note:
is similar to the short form of
.codn defset .
+.coNP Accessor @ read-once
+.synb
+.mets (read-once << expression )
+.mets (set (read-once << place ) << new-value )
+.syne
+.desc
+When the
+.code read-once
+accessor is invoked as a function, it behaves like
+.codn identity ,
+simply returning the value of
+.metn expression ,
+which is not required to be a syntactic place.
+
+If a
+.code read-once
+form is used as a syntactic place then its argument must also be a
+.metn place .
+The
+.code read-once
+syntactic place denotes the same place as the enclosed
+.code place
+form, but with somewhat altered semantics, which is most useful in conjunction
+with
+.codn placelet ,
+and in writing place-mutating macros which make multiple accesses to a place.
+
+Firstly, if the
+.code read-once
+place is evaluated, it accesses the existing value of
+.meta place
+exactly once, even if it occurs in a place-mutating form which
+normally doesn't use the prior value, such as the
+.code set
+macro.
+
+When
+.code read-once
+accesses
+.metn place ,
+it stores the value in a hidden variable.
+Then, within the same place-mutating form, multiple references to the same
+.code read-once
+form all access the value of this hidden variable.
+Whenever the
+.code read-once
+form is assigned, both the the hidden variable and the underlying
+.meta place
+receive the new value.
+
+Multiple references to the same
+.code read-once
+form can be produced using the
+.code placelet
+or
+.code placelet*
+macros, or by making multiple calls to the getter function obtained using
+.code with-update-expander
+in the implementation of a user-defined place-mutating operator,
+or user-defined place.
+
+.TP* Example:
+
+In both of the following two examples, there is no question that the
+.code array
+and
+.code i
+expressions are themselves evaluated only once; the issue is the access to the
+array itself; under the plain placelet, the array referencing takes place more
+times.
+
+.verb
+ ;; without read-once, array element [array i] is
+ ;; accessed twice to fetch its current value: once
+ ;; in the plusp expression, and then once again in
+ ;; the dec expression.
+
+ (placelet ((cell [array i]))
+ (if (plusp cell)
+ (dec cell)))
+
+ ;; with read-once, it is accessed once. plusp refers
+ ;; to a hidden lexical variable to obtain the prior
+ ;; value, and so does dec. dec stores the new value
+ ;; through to [array i] and the hidden variable.
+
+ (placelet ((cell (read-once [array i])))
+ (if (plusp cell)
+ (dec cell)))
+.brev
+
+The following is
+.B not
+an example of multiple references to the same
+.code read-once
+form:
+
+.verb
+ (defmacro inc-positive (place)
+ ^(if (plusp (read-once ,place))
+ (inc (read-once ,place))))
+.brev
+
+Here, even though the
+.code read-once
+forms may be structurally identical, they are separate instances.
+The first instance isn't even a syntactic place, but a call to the
+.code read-once
+function. Multiple references to the same place can only be
+generated using
+.code placelet
+or else by multiple explicit calls to the same getter function or macro
+generated for a place by an update expander.
+
+The following is a corrected version of
+.codn inc-positive :
+
+.verb
+ (defmacro inc-positive (place :env env)
+ (with-update-expander (getter setter) ^(read-once ,place) env
+ ^(if (plusp (,getter))
+ (,setter (succ (,getter))))))
+.brev
+
+To write the macro without
+.code read-once
+requires that it handles the job of providing a temporary variable
+for the value:
+
+.verb
+ (defmacro inc-positive (place :env e)
+ (with-update-expander (getter setter) place env
+ (with-gensym (value)
+ ^(slet ((,value (,getter)))
+ ^(if (plusp ,value)
+ (,setter (succ ,value)))))))
+.brev
+
+The
+.code read-once
+accessor wrapped around
+.meta place
+allows
+.code inc-positive
+to simply make multiple references to
+.code "(,getter)"
+which will cache the value; the macro deosn't have to to introduce its own
+hidden caching variable.
+
.coNP Special variables @, *place-update-expander* @ *place-clobber-expander* and @ *place-delete-expander*
.desc
These variables hold hash tables, by means of which update expanders,