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
|
* All augmented assignment operators (except `%=` and `^=`) now allow
|
||||||
more than two arguments.
|
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
|
Bug Fixes
|
||||||
------------------------------
|
------------------------------
|
||||||
* Statements in the second argument of `assert` are now executed.
|
* 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
|
left-hand-side object, a useful behavior when you want to concatenate Hy
|
||||||
objects in a macro, for instance.
|
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:
|
.. _hylist:
|
||||||
|
|
||||||
@ -90,11 +94,6 @@ HyDict
|
|||||||
``hy.models.HyDict`` inherits :ref:`HySequence` for curly-bracketed
|
``hy.models.HyDict`` inherits :ref:`HySequence` for curly-bracketed
|
||||||
``{}`` expressions, which compile down to a Python dictionary literal.
|
``{}`` 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
|
Atomic Models
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -1345,17 +1345,16 @@ class HyASTCompiler(object):
|
|||||||
|
|
||||||
def _compile_assign(self, root, name, result):
|
def _compile_assign(self, root, name, result):
|
||||||
|
|
||||||
str_name = "%s" % name
|
if name in [HySymbol(x) for x in ("None", "True", "False")]:
|
||||||
if str_name in ("None", "True", "False"):
|
|
||||||
raise self._syntax_error(name,
|
raise self._syntax_error(name,
|
||||||
"Can't assign to `%s'" % str_name)
|
"Can't assign to `{}'".format(name))
|
||||||
|
|
||||||
result = self.compile(result)
|
result = self.compile(result)
|
||||||
ld_name = self.compile(name)
|
ld_name = self.compile(name)
|
||||||
|
|
||||||
if isinstance(ld_name.expr, ast.Call):
|
if isinstance(ld_name.expr, ast.Call):
|
||||||
raise self._syntax_error(name,
|
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
|
if (result.temp_variables
|
||||||
and isinstance(name, HySymbol)
|
and isinstance(name, HySymbol)
|
||||||
|
@ -144,7 +144,7 @@
|
|||||||
(hy-repr (dict x)))))
|
(hy-repr (dict x)))))
|
||||||
|
|
||||||
(for [[types fmt] (partition [
|
(for [[types fmt] (partition [
|
||||||
list "[...]"
|
[list HyList] "[...]"
|
||||||
[set HySet] "#{...}"
|
[set HySet] "#{...}"
|
||||||
frozenset "(frozenset #{...})"
|
frozenset "(frozenset #{...})"
|
||||||
dict-keys "(dict-keys [...])"
|
dict-keys "(dict-keys [...])"
|
||||||
|
@ -70,9 +70,6 @@
|
|||||||
(with-decorator (method-decorator ~name)
|
(with-decorator (method-decorator ~name)
|
||||||
(defn ~name ~params ~@body))))
|
(defn ~name ~params ~@body))))
|
||||||
|
|
||||||
(defn head-tail [l]
|
|
||||||
(, (get l 0) (cut l 1)))
|
|
||||||
|
|
||||||
(defmacro defn [name &rest bodies]
|
(defmacro defn [name &rest bodies]
|
||||||
(setv arity-overloaded? (fn [bodies]
|
(setv arity-overloaded? (fn [bodies]
|
||||||
(if (isinstance (first bodies) HyString)
|
(if (isinstance (first bodies) HyString)
|
||||||
@ -83,14 +80,10 @@
|
|||||||
(do
|
(do
|
||||||
(setv comment (HyString))
|
(setv comment (HyString))
|
||||||
(if (= (type (first bodies)) HyString)
|
(if (= (type (first bodies)) HyString)
|
||||||
(setv [comment bodies] (head-tail bodies)))
|
(setv [comment #* bodies] bodies))
|
||||||
(setv ret `(do))
|
(+ '(do (import [hy.contrib.multi [MultiDispatch]])) (lfor
|
||||||
(.append ret '(import [hy.contrib.multi [MultiDispatch]]))
|
[let-binds #* body] bodies
|
||||||
(for [body bodies]
|
`(with-decorator MultiDispatch (defn ~name ~let-binds ~comment ~@body)))))
|
||||||
(setv [let-binds body] (head-tail body))
|
|
||||||
(.append ret
|
|
||||||
`(with-decorator MultiDispatch (defn ~name ~let-binds ~comment ~@body))))
|
|
||||||
ret)
|
|
||||||
(do
|
(do
|
||||||
(setv [lambda-list body] (head-tail bodies))
|
(setv [lambda-list #* body] bodies)
|
||||||
`(setv ~name (fn* ~lambda-list ~@body)))))
|
`(setv ~name (fn* ~lambda-list ~@body)))))
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
;; license. See the LICENSE.
|
;; license. See the LICENSE.
|
||||||
|
|
||||||
(import [hy [HyExpression HyDict]]
|
(import [hy [HyExpression HyDict]]
|
||||||
|
[hy.models [HySequence]]
|
||||||
[functools [partial]]
|
[functools [partial]]
|
||||||
[importlib [import-module]]
|
[importlib [import-module]]
|
||||||
[collections [OrderedDict]]
|
[collections [OrderedDict]]
|
||||||
@ -17,9 +18,7 @@
|
|||||||
(cond
|
(cond
|
||||||
[(instance? HyExpression form)
|
[(instance? HyExpression form)
|
||||||
(outer (HyExpression (map inner form)))]
|
(outer (HyExpression (map inner form)))]
|
||||||
[(instance? HyDict form)
|
[(or (instance? HySequence form) (list? form))
|
||||||
(HyDict (outer (HyExpression (map inner form))))]
|
|
||||||
[(list? form)
|
|
||||||
((type form) (outer (HyExpression (map inner form))))]
|
((type form) (outer (HyExpression (map inner form))))]
|
||||||
[(coll? form)
|
[(coll? form)
|
||||||
(walk inner outer (list form))]
|
(walk inner outer (list form))]
|
||||||
@ -46,22 +45,24 @@
|
|||||||
(setv module (or (and module-name
|
(setv module (or (and module-name
|
||||||
(import-module module-name))
|
(import-module module-name))
|
||||||
(calling-module))
|
(calling-module))
|
||||||
quote-level [0]
|
quote-level 0
|
||||||
ast-compiler (HyASTCompiler module)) ; TODO: make nonlocal after dropping Python2
|
ast-compiler (HyASTCompiler module)) ; TODO: make nonlocal after dropping Python2
|
||||||
(defn traverse [form]
|
(defn traverse [form]
|
||||||
(walk expand identity form))
|
(walk expand identity form))
|
||||||
(defn expand [form]
|
(defn expand [form]
|
||||||
|
(nonlocal quote-level)
|
||||||
;; manages quote levels
|
;; manages quote levels
|
||||||
(defn +quote [&optional [x 1]]
|
(defn +quote [&optional [x 1]]
|
||||||
|
(nonlocal quote-level)
|
||||||
(setv head (first form))
|
(setv head (first form))
|
||||||
(+= (get quote-level 0) x)
|
(+= quote-level x)
|
||||||
(when (neg? (get quote-level 0))
|
(when (neg? quote-level)
|
||||||
(raise (TypeError "unquote outside of quasiquote")))
|
(raise (TypeError "unquote outside of quasiquote")))
|
||||||
(setv res (traverse (cut form 1)))
|
(setv res (traverse (cut form 1)))
|
||||||
(-= (get quote-level 0) x)
|
(-= quote-level x)
|
||||||
`(~head ~@res))
|
`(~head ~@res))
|
||||||
(if (call? form)
|
(if (call? form)
|
||||||
(cond [(get quote-level 0)
|
(cond [quote-level
|
||||||
(cond [(in (first form) '[unquote unquote-splice])
|
(cond [(in (first form) '[unquote unquote-splice])
|
||||||
(+quote -1)]
|
(+quote -1)]
|
||||||
[(= (first form) 'quasiquote) (+quote)]
|
[(= (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.
|
returns an OrderedDict mapping headers to sublists.
|
||||||
Arguments without a header are under None.
|
Arguments without a header are under None.
|
||||||
"
|
"
|
||||||
(setv headers '[&optional &rest &kwonly &kwargs]
|
(setv headers ['&optional '&rest '&kwonly '&kwargs]
|
||||||
sections (OrderedDict [(, None [])])
|
sections (OrderedDict [(, None [])])
|
||||||
header None)
|
header None)
|
||||||
(for [arg form]
|
(for [arg form]
|
||||||
@ -169,7 +170,7 @@ Arguments without a header are under None.
|
|||||||
#{})))))
|
#{})))))
|
||||||
(defn handle-args-list [self]
|
(defn handle-args-list [self]
|
||||||
(setv protected #{}
|
(setv protected #{}
|
||||||
argslist `[])
|
argslist [])
|
||||||
(for [[header section] (-> self (.tail) first lambda-list .items)]
|
(for [[header section] (-> self (.tail) first lambda-list .items)]
|
||||||
(if header (.append argslist header))
|
(if header (.append argslist header))
|
||||||
(cond [(in header [None '&rest '&kwargs])
|
(cond [(in header [None '&rest '&kwargs])
|
||||||
|
@ -48,16 +48,14 @@ be associated in pairs."
|
|||||||
|
|
||||||
|
|
||||||
(defn _with [node args body]
|
(defn _with [node args body]
|
||||||
(if (not (empty? args))
|
(if
|
||||||
(do
|
(not args)
|
||||||
(if (>= (len args) 2)
|
`(do ~@body)
|
||||||
(do
|
(<= (len args) 2)
|
||||||
(setv p1 (.pop args 0)
|
`(~node [~@args] ~@body)
|
||||||
p2 (.pop args 0)
|
True (do
|
||||||
primary [p1 p2])
|
(setv [p1 p2 #* args] args)
|
||||||
`(~node [~@primary] ~(_with node args body)))
|
`(~node [~p1 ~p2] ~(_with node args body)))))
|
||||||
`(~node [~@args] ~@body)))
|
|
||||||
`(do ~@body)))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro with [args &rest 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
|
The result in the bracket may be omitted, in which case the condition is also
|
||||||
used as the result."
|
used as the result."
|
||||||
(if (empty? branches)
|
(or branches
|
||||||
None
|
(return))
|
||||||
(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
|
|
||||||
(setv g (gensym))
|
|
||||||
`(if (do (setv ~g ~(first branch)) ~g) ~g))
|
|
||||||
`(if ~(first branch) (do ~@(cut branch 1)))))
|
|
||||||
|
|
||||||
(setv root (check-branch branch))
|
`(if ~@(reduce + (gfor
|
||||||
(setv latest-branch root)
|
branch branches
|
||||||
|
(if
|
||||||
(for [branch branches]
|
(not (and (is (type branch) hy.HyList) branch))
|
||||||
(setv cur-branch (check-branch branch))
|
(macro-error branch "each cond branch needs to be a nonempty list")
|
||||||
(.append latest-branch cur-branch)
|
(= (len branch) 1) (do
|
||||||
(setv latest-branch cur-branch))
|
(setv g (gensym))
|
||||||
root)))
|
[`(do (setv ~g ~(first branch)) ~g) g])
|
||||||
|
True
|
||||||
|
[(first branch) `(do ~@(cut branch 1))])))))
|
||||||
|
|
||||||
|
|
||||||
(defmacro -> [head &rest args]
|
(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]
|
(defn extract-o!-sym [arg]
|
||||||
(cond [(and (symbol? arg) (.startswith arg "o!"))
|
(cond [(and (symbol? arg) (.startswith arg "o!"))
|
||||||
arg]
|
arg]
|
||||||
[(and (list? arg) (.startswith (first arg) "o!"))
|
[(and (instance? HyList arg) (.startswith (first arg) "o!"))
|
||||||
(first arg)]))
|
(first arg)]))
|
||||||
(setv os (list (filter identity (map extract-o!-sym args)))
|
(setv os (list (filter identity (map extract-o!-sym args)))
|
||||||
gs (lfor s os (HySymbol (+ "g!" (cut s 2)))))
|
gs (lfor s os (HySymbol (+ "g!" (cut s 2)))))
|
||||||
|
@ -243,7 +243,7 @@ class HyComplex(HyObject, complex):
|
|||||||
_wrappers[complex] = HyComplex
|
_wrappers[complex] = HyComplex
|
||||||
|
|
||||||
|
|
||||||
class HySequence(HyObject, list):
|
class HySequence(HyObject, tuple):
|
||||||
"""
|
"""
|
||||||
An abstract type for sequence-like models to inherit from.
|
An abstract type for sequence-like models to inherit from.
|
||||||
"""
|
"""
|
||||||
@ -256,7 +256,8 @@ class HySequence(HyObject, list):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __add__(self, other):
|
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):
|
def __getslice__(self, start, end):
|
||||||
return self.__class__(super(HySequence, self).__getslice__(start, end))
|
return self.__class__(super(HySequence, self).__getslice__(start, end))
|
||||||
@ -326,10 +327,10 @@ class HyDict(HySequence):
|
|||||||
return '' + g("HyDict()")
|
return '' + g("HyDict()")
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
return self[0::2]
|
return list(self[0::2])
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
return self[1::2]
|
return list(self[1::2])
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
return list(zip(self.keys(), self.values()))
|
return list(zip(self.keys(), self.values()))
|
||||||
|
@ -21,17 +21,16 @@
|
|||||||
walk-form)))
|
walk-form)))
|
||||||
|
|
||||||
(defn test-walk []
|
(defn test-walk []
|
||||||
(setv acc '())
|
(setv acc [])
|
||||||
(assert (= (walk (partial collector acc) identity walk-form)
|
(assert (= (list (walk (partial collector acc) identity walk-form))
|
||||||
[None None]))
|
[None None]))
|
||||||
(assert (= acc walk-form))
|
(assert (= acc (list walk-form)))
|
||||||
(setv acc [])
|
(setv acc [])
|
||||||
(assert (= (walk identity (partial collector acc) walk-form)
|
(assert (= (walk identity (partial collector acc) walk-form)
|
||||||
None))
|
None))
|
||||||
(assert (= acc [walk-form])))
|
(assert (= acc [walk-form])))
|
||||||
|
|
||||||
(defn test-walk-iterators []
|
(defn test-walk-iterators []
|
||||||
(setv acc [])
|
|
||||||
(assert (= (walk (fn [x] (* 2 x)) (fn [x] x)
|
(assert (= (walk (fn [x] (* 2 x)) (fn [x] x)
|
||||||
(drop 1 [1 [2 [3 [4]]]]))
|
(drop 1 [1 [2 [3 [4]]]]))
|
||||||
[[2 [3 [4]] 2 [3 [4]]]])))
|
[[2 [3 [4]] 2 [3 [4]]]])))
|
||||||
@ -44,19 +43,19 @@
|
|||||||
(assert (= (macroexpand-all '(foo-walk))
|
(assert (= (macroexpand-all '(foo-walk))
|
||||||
42))
|
42))
|
||||||
(assert (= (macroexpand-all '(with [a 1]))
|
(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)))
|
(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]
|
(assert (= (macroexpand-all '(with [a 1]
|
||||||
'(with [b 2])
|
'(with [b 2])
|
||||||
`(with [c 3]
|
`(with [c 3]
|
||||||
~(with [d 4])
|
~(with [d 4])
|
||||||
~@[(with [e 5])])))
|
~@[(with [e 5])])))
|
||||||
'(with* [a 1]
|
'(with* [a 1]
|
||||||
(do '(with [b 2])
|
'(with [b 2])
|
||||||
`(with [c 3]
|
`(with [c 3]
|
||||||
~(with* [d 4] (do))
|
~(with* [d 4])
|
||||||
~@[(with* [e 5] (do))])))))
|
~@[(with* [e 5])]))))
|
||||||
|
|
||||||
(defmacro require-macro []
|
(defmacro require-macro []
|
||||||
`(do
|
`(do
|
||||||
@ -130,7 +129,7 @@
|
|||||||
'(foo `(bar a ~a ~"x"))))
|
'(foo `(bar a ~a ~"x"))))
|
||||||
(assert (= `(foo ~@[a])
|
(assert (= `(foo ~@[a])
|
||||||
'(foo "x")))
|
'(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"))))))
|
'(foo `(bar [a] ~@[a] ~@["x" a a] ~"x"))))))
|
||||||
|
|
||||||
(defn test-let-except []
|
(defn test-let-except []
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"NATIVE: test for quoting functionality"
|
"NATIVE: test for quoting functionality"
|
||||||
(setv q (quote (a b c)))
|
(setv q (quote (a b c)))
|
||||||
(assert (= (len q) 3))
|
(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 []
|
(defn test-basic-quoting []
|
||||||
|
@ -56,8 +56,8 @@ def test_list_add():
|
|||||||
a = HyList([1, 2, 3])
|
a = HyList([1, 2, 3])
|
||||||
b = HyList([3, 4, 5])
|
b = HyList([3, 4, 5])
|
||||||
c = a + b
|
c = a + b
|
||||||
assert c == [1, 2, 3, 3, 4, 5]
|
assert c == HyList([1, 2, 3, 3, 4, 5])
|
||||||
assert c.__class__ == HyList
|
assert type(c) is HyList
|
||||||
|
|
||||||
|
|
||||||
def test_list_slice():
|
def test_list_slice():
|
||||||
@ -91,7 +91,7 @@ hyset = HySet([3, 1, 2, 2])
|
|||||||
|
|
||||||
|
|
||||||
def test_set():
|
def test_set():
|
||||||
assert hyset == [3, 1, 2, 2]
|
assert list(hyset) == [3, 1, 2, 2]
|
||||||
|
|
||||||
|
|
||||||
def test_number_model_copy():
|
def test_number_model_copy():
|
||||||
|
Loading…
Reference in New Issue
Block a user