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 * 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.

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 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
------------- -------------

View File

@ -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)

View File

@ -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 [...])"

View File

@ -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)))))

View File

@ -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])

View File

@ -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)))))

View File

@ -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()))

View File

@ -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 []

View File

@ -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 []

View File

@ -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():