Merge pull request #1804 from Kodiologist/immutable-models
Immutable Hy models
This commit is contained in:
commit
ee35d61e58
6
NEWS.rst
6
NEWS.rst
@ -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.
|
||||
|
@ -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
|
||||
-------------
|
||||
|
||||
|
@ -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)
|
||||
|
@ -144,7 +144,7 @@
|
||||
(hy-repr (dict x)))))
|
||||
|
||||
(for [[types fmt] (partition [
|
||||
list "[...]"
|
||||
[list HyList] "[...]"
|
||||
[set HySet] "#{...}"
|
||||
frozenset "(frozenset #{...})"
|
||||
dict-keys "(dict-keys [...])"
|
||||
|
@ -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)))))
|
||||
|
@ -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])
|
||||
|
@ -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)))))
|
||||
|
@ -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()))
|
||||
|
@ -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 []
|
||||
|
@ -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 []
|
||||
|
@ -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():
|
||||
|
Loading…
Reference in New Issue
Block a user