Open davazp opened 5 years ago
Object.freeze
will of course not signal when an attempt is made to mutate a property.
However, if you were to target ECMA6, Proxy Objects would allow you to intercept this (although they wrap a single reference only, so you would have to deep-copy the whole list this way).
Proxy does have a noticeable performance hit, so perhaps if you did go this route it perhaps should not happen when you (declare (optimize safety 1))
.
Object.freeze
will of course not signal when an attempt is made to mutate a property.This is not exactly the case. as an example:
Another question is whether it is necessary to change the property # ()?
In the example I described earlier, it is enough to use a local type declaration:
(let ((v (vector))
...)
or
(let ((v (make-array 0))
...)
Such a property is a tricky feature.
Which can be used in unexpected ways. This is a plus. And about the minuses we must clearly say. Who uses it, knows what he is doing and what the result may be.
Note that CCL when compiling the function, no message is display , like SBCL.
Resume: Tend to think of it as a useful tricky feature for conscious use.
Good catch @vlad-km, I think the difference I was noticing was that I was not executing that freeze and mutation in strict mode (which jscl uses by default iirc). This is of course great news in terms of performance and code complexity.
Bikeshedding your example, it is acceptable to me for #()
to be an immutable singleton, returned by the relevant constructors. I would assume (eq #() (vector))
as an implementation detail here.
Resume: Tend to think of it as a useful tricky feature for conscious use.
For example
(defmacro genmakv (name catch &rest initials)
(let ((g!name (gensym (symbol-name name)))
(c!name (intern (jscl::concat "MAK-" (symbol-name name))))
(a!name (intern (jscl::concat "PUT-" (symbol-name name))))
(h!code)
(ini!code))
(if catch
(setq h!code `(handler-case
(progn ((jscl::oget ,g!name "push") value) value)
(error (msg)
(values))))
(setq h!code `((jscl::oget ,g!name "push") value)))
(if initials
(setq ini!code
`(progn
(map 'nil (lambda (x) ((jscl::oget ,g!name "push") x)) ',initials))))
`(let ((,g!name #()))
,ini!code
(defun ,c!name (&key freeze)
(if freeze (#j:Object:freeze ,g!name))
,g!name)
(defun ,a!name (value)
,h!code
value))))
CL-USER> (genmakv v0 t 1 2 3)
PUT-V0
CL-USER> (genmakv v1 nil a b c)
PUT-V1
CL-USER> (mak-v0)
#(1 2 3)
CL-USER> (mak-v1)
#(A B C)
CL-USER> (eq (mak-v0)(mak-v1))
NIL
CL-USER> (put-v1 (lambda (x) x))
#<FUNCTION>
CL-USER> (put-v0 (list 'a 'b 'c))
(A B C)
CL-USER> (mak-v0)
#(1 2 3 (A B C))
CL-USER> (mak-v1)
#(A B C #<FUNCTION>)
CL-USER> (mak-v1 :freeze t)
#(A B C #<FUNCTION>)
CL-USER> (put-v1 (lambda (x) x))
ERROR: Cannot add property 4, object is not extensible
CL-USER> (mak-v0 :freeze t)
#(1 2 3 (A B C))
CL-USER> (put-v0 (list 'a 'b 'c))
(A B C)
CL-USER> (mak-v0)
#(1 2 3 (A B C))
CL-USER> (mak-v1)
#(A B C #<FUNCTION>)
Do you want to singleton
?
(let ((guard nil))
(defmacro mak-singl (name catch &rest init)
(let ((code))
(if guard (error "There must be only one :~a" guard))
(setq guard `,name)
(setq code
`(progn
(genmakv ,name ,catch ,@init)))
`,code)))
CL-USER> (mak-singl mclaud t 1 2 3)
PUT-MCLAUD
CL-USER> (mak-singl victor t 1 2 3)
ERROR: There must be only one :MCLAUD
CL-USER> (mak-mclaud)
#(1 2 3)
I would assume
(eq #() (vector))
as an implementation detail here.
Ah, #()
is not a string nor a bit-vector, I understand. I'd forgotten that oddity with equal
vs equalp
.
Currently, literal values are mutable. For example, in
f
will modify the literal vector, which is always the same object. Making this function stateful. Some implementations mark literal values as read-only to avoid that, as SBCL for example, which will trigger a warning likeand will ignore the mutation completely. We could achieve something similar with
Object.freeze
.See https://github.com/jscl-project/jscl/issues/345#issuecomment-450793913