diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2021-10-26 02:33:01 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2021-10-26 02:33:01 -0700 |
commit | 30638a91a2c47acd08cf67735eac31e5adcf4eba (patch) | |
tree | bac6ac5c1f3cd4eba8fb2c52c0ef9a01f1649e28 | |
parent | 02a73c7785f5cdd7975cf10f83b430dfcc6778a3 (diff) | |
download | txr-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.c | 4 | ||||
-rw-r--r-- | stdlib/doc-syms.tl | 1 | ||||
-rw-r--r-- | stdlib/place.tl | 11 | ||||
-rw-r--r-- | txr.1 | 170 |
4 files changed, 184 insertions, 2 deletions
@@ -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)))) @@ -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, |