Merge pull request #1804 from Kodiologist/immutable-models

Immutable Hy models
This commit is contained in:
Kodi Arfer 2019-08-18 09:49:45 -04:00 committed by GitHub
commit ee35d61e58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 70 additions and 84 deletions

View File

@ -16,6 +16,12 @@ New Features
* All augmented assignment operators (except `%=` and `^=`) now allow
more than two arguments.
Other Breaking Changes
------------------------------
* ``HySequence`` is now a subclass of ``tuple`` instead of ``list``.
Thus, a ``HyList`` will never be equal to a ``list``, and you can't
use ``.append``, ``.pop``, etc. on an expression or list.
Bug Fixes
------------------------------
* Statements in the second argument of `assert` are now executed.

View File

@ -60,6 +60,10 @@ Adding a HySequence to another iterable object reuses the class of the
left-hand-side object, a useful behavior when you want to concatenate Hy
objects in a macro, for instance.
HySequences are (mostly) immutable: you can't add, modify, or remove
elements. You can still append to a variable containing a HySequence with
``+=`` and otherwise construct new HySequences out of old ones.
.. _hylist:
@ -90,11 +94,6 @@ HyDict
``hy.models.HyDict`` inherits :ref:`HySequence` for curly-bracketed
``{}`` expressions, which compile down to a Python dictionary literal.
The decision of using a list instead of a dict as the base class for
``HyDict`` allows easier manipulation of dicts in macros, with the added
benefit of allowing compound expressions as dict keys (as, for instance,
the :ref:`HyExpression` Python class isn't hashable).
Atomic Models
-------------

View File

@ -1345,17 +1345,16 @@ class HyASTCompiler(object):
def _compile_assign(self, root, name, result):
str_name = "%s" % name
if str_name in ("None", "True", "False"):
if name in [HySymbol(x) for x in ("None", "True", "False")]:
raise self._syntax_error(name,
"Can't assign to `%s'" % str_name)
"Can't assign to `{}'".format(name))
result = self.compile(result)
ld_name = self.compile(name)
if isinstance(ld_name.expr, ast.Call):
raise self._syntax_error(name,
"Can't assign to a callable: `%s'" % str_name)
"Can't assign to a callable: `{}'".format(name))
if (result.temp_variables
and isinstance(name, HySymbol)

View File

@ -144,7 +144,7 @@
(hy-repr (dict x)))))
(for [[types fmt] (partition [
list "[...]"
[list HyList] "[...]"
[set HySet] "#{...}"
frozenset "(frozenset #{...})"
dict-keys "(dict-keys [...])"

View File

@ -70,9 +70,6 @@
(with-decorator (method-decorator ~name)
(defn ~name ~params ~@body))))
(defn head-tail [l]
(, (get l 0) (cut l 1)))
(defmacro defn [name &rest bodies]
(setv arity-overloaded? (fn [bodies]
(if (isinstance (first bodies) HyString)
@ -83,14 +80,10 @@
(do
(setv comment (HyString))
(if (= (type (first bodies)) HyString)
(setv [comment bodies] (head-tail bodies)))
(setv ret `(do))
(.append ret '(import [hy.contrib.multi [MultiDispatch]]))
(for [body bodies]
(setv [let-binds body] (head-tail body))
(.append ret
`(with-decorator MultiDispatch (defn ~name ~let-binds ~comment ~@body))))
ret)
(setv [comment #* bodies] bodies))
(+ '(do (import [hy.contrib.multi [MultiDispatch]])) (lfor
[let-binds #* body] bodies
`(with-decorator MultiDispatch (defn ~name ~let-binds ~comment ~@body)))))
(do
(setv [lambda-list body] (head-tail bodies))
(setv [lambda-list #* body] bodies)
`(setv ~name (fn* ~lambda-list ~@body)))))

View File

@ -4,6 +4,7 @@
;; license. See the LICENSE.
(import [hy [HyExpression HyDict]]
[hy.models [HySequence]]
[functools [partial]]
[importlib [import-module]]
[collections [OrderedDict]]
@ -17,9 +18,7 @@
(cond
[(instance? HyExpression form)
(outer (HyExpression (map inner form)))]
[(instance? HyDict form)
(HyDict (outer (HyExpression (map inner form))))]
[(list? form)
[(or (instance? HySequence form) (list? form))
((type form) (outer (HyExpression (map inner form))))]
[(coll? form)
(walk inner outer (list form))]
@ -46,22 +45,24 @@
(setv module (or (and module-name
(import-module module-name))
(calling-module))
quote-level [0]
quote-level 0
ast-compiler (HyASTCompiler module)) ; TODO: make nonlocal after dropping Python2
(defn traverse [form]
(walk expand identity form))
(defn expand [form]
(nonlocal quote-level)
;; manages quote levels
(defn +quote [&optional [x 1]]
(nonlocal quote-level)
(setv head (first form))
(+= (get quote-level 0) x)
(when (neg? (get quote-level 0))
(+= quote-level x)
(when (neg? quote-level)
(raise (TypeError "unquote outside of quasiquote")))
(setv res (traverse (cut form 1)))
(-= (get quote-level 0) x)
(-= quote-level x)
`(~head ~@res))
(if (call? form)
(cond [(get quote-level 0)
(cond [quote-level
(cond [(in (first form) '[unquote unquote-splice])
(+quote -1)]
[(= (first form) 'quasiquote) (+quote)]
@ -89,7 +90,7 @@ splits a fn argument list into sections based on &-headers.
returns an OrderedDict mapping headers to sublists.
Arguments without a header are under None.
"
(setv headers '[&optional &rest &kwonly &kwargs]
(setv headers ['&optional '&rest '&kwonly '&kwargs]
sections (OrderedDict [(, None [])])
header None)
(for [arg form]
@ -169,7 +170,7 @@ Arguments without a header are under None.
#{})))))
(defn handle-args-list [self]
(setv protected #{}
argslist `[])
argslist [])
(for [[header section] (-> self (.tail) first lambda-list .items)]
(if header (.append argslist header))
(cond [(in header [None '&rest '&kwargs])

View File

@ -48,16 +48,14 @@ be associated in pairs."
(defn _with [node args body]
(if (not (empty? args))
(do
(if (>= (len args) 2)
(do
(setv p1 (.pop args 0)
p2 (.pop args 0)
primary [p1 p2])
`(~node [~@primary] ~(_with node args body)))
`(~node [~@args] ~@body)))
`(do ~@body)))
(if
(not args)
`(do ~@body)
(<= (len args) 2)
`(~node [~@args] ~@body)
True (do
(setv [p1 p2 #* args] args)
`(~node [~p1 ~p2] ~(_with node args body)))))
(defmacro with [args &rest body]
@ -87,29 +85,19 @@ Shorthand for nested with/a* loops:
The result in the bracket may be omitted, in which case the condition is also
used as the result."
(if (empty? branches)
None
(do
(setv branches (iter branches))
(setv branch (next branches))
(defn check-branch [branch]
"check `cond` branch for validity, return the corresponding `if` expr"
(if (not (= (type branch) HyList))
(macro-error branch "cond branches need to be a list"))
(if (< (len branch) 2)
(do
(or branches
(return))
`(if ~@(reduce + (gfor
branch branches
(if
(not (and (is (type branch) hy.HyList) branch))
(macro-error branch "each cond branch needs to be a nonempty list")
(= (len branch) 1) (do
(setv g (gensym))
`(if (do (setv ~g ~(first branch)) ~g) ~g))
`(if ~(first branch) (do ~@(cut branch 1)))))
(setv root (check-branch branch))
(setv latest-branch root)
(for [branch branches]
(setv cur-branch (check-branch branch))
(.append latest-branch cur-branch)
(setv latest-branch cur-branch))
root)))
[`(do (setv ~g ~(first branch)) ~g) g])
True
[(first branch) `(do ~@(cut branch 1))])))))
(defmacro -> [head &rest args]
@ -213,7 +201,7 @@ Such 'o!' params are available within `body` as the equivalent 'g!' symbol."
(defn extract-o!-sym [arg]
(cond [(and (symbol? arg) (.startswith arg "o!"))
arg]
[(and (list? arg) (.startswith (first arg) "o!"))
[(and (instance? HyList arg) (.startswith (first arg) "o!"))
(first arg)]))
(setv os (list (filter identity (map extract-o!-sym args)))
gs (lfor s os (HySymbol (+ "g!" (cut s 2)))))

View File

@ -243,7 +243,7 @@ class HyComplex(HyObject, complex):
_wrappers[complex] = HyComplex
class HySequence(HyObject, list):
class HySequence(HyObject, tuple):
"""
An abstract type for sequence-like models to inherit from.
"""
@ -256,7 +256,8 @@ class HySequence(HyObject, list):
return self
def __add__(self, other):
return self.__class__(super(HySequence, self).__add__(other))
return self.__class__(super(HySequence, self).__add__(
tuple(other) if isinstance(other, list) else other))
def __getslice__(self, start, end):
return self.__class__(super(HySequence, self).__getslice__(start, end))
@ -326,10 +327,10 @@ class HyDict(HySequence):
return '' + g("HyDict()")
def keys(self):
return self[0::2]
return list(self[0::2])
def values(self):
return self[1::2]
return list(self[1::2])
def items(self):
return list(zip(self.keys(), self.values()))

View File

@ -21,17 +21,16 @@
walk-form)))
(defn test-walk []
(setv acc '())
(assert (= (walk (partial collector acc) identity walk-form)
(setv acc [])
(assert (= (list (walk (partial collector acc) identity walk-form))
[None None]))
(assert (= acc walk-form))
(assert (= acc (list walk-form)))
(setv acc [])
(assert (= (walk identity (partial collector acc) walk-form)
None))
(assert (= acc [walk-form])))
(defn test-walk-iterators []
(setv acc [])
(assert (= (walk (fn [x] (* 2 x)) (fn [x] x)
(drop 1 [1 [2 [3 [4]]]]))
[[2 [3 [4]] 2 [3 [4]]]])))
@ -44,19 +43,19 @@
(assert (= (macroexpand-all '(foo-walk))
42))
(assert (= (macroexpand-all '(with [a 1]))
'(with* [a 1] (do))))
'(with* [a 1])))
(assert (= (macroexpand-all '(with [a 1 b 2 c 3] (for [d c] foo)))
'(with* [a 1] (with* [b 2] (with* [c 3] (do (for [d c] foo)))))))
'(with* [a 1] (with* [b 2] (with* [c 3] (for [d c] foo))))))
(assert (= (macroexpand-all '(with [a 1]
'(with [b 2])
`(with [c 3]
~(with [d 4])
~@[(with [e 5])])))
'(with* [a 1]
(do '(with [b 2])
'(with [b 2])
`(with [c 3]
~(with* [d 4] (do))
~@[(with* [e 5] (do))])))))
~(with* [d 4])
~@[(with* [e 5])]))))
(defmacro require-macro []
`(do
@ -130,7 +129,7 @@
'(foo `(bar a ~a ~"x"))))
(assert (= `(foo ~@[a])
'(foo "x")))
(assert (= `(foo `(bar [a] ~@[a] ~@~[a 'a `a] ~~@[a]))
(assert (= `(foo `(bar [a] ~@[a] ~@~(HyList [a 'a `a]) ~~@[a]))
'(foo `(bar [a] ~@[a] ~@["x" a a] ~"x"))))))
(defn test-let-except []

View File

@ -9,7 +9,7 @@
"NATIVE: test for quoting functionality"
(setv q (quote (a b c)))
(assert (= (len q) 3))
(assert (= q [(quote a) (quote b) (quote c)])))
(assert (= q (HyExpression [(quote a) (quote b) (quote c)]))))
(defn test-basic-quoting []

View File

@ -56,8 +56,8 @@ def test_list_add():
a = HyList([1, 2, 3])
b = HyList([3, 4, 5])
c = a + b
assert c == [1, 2, 3, 3, 4, 5]
assert c.__class__ == HyList
assert c == HyList([1, 2, 3, 3, 4, 5])
assert type(c) is HyList
def test_list_slice():
@ -91,7 +91,7 @@ hyset = HySet([3, 1, 2, 2])
def test_set():
assert hyset == [3, 1, 2, 2]
assert list(hyset) == [3, 1, 2, 2]
def test_number_model_copy():