diff --git a/NEWS.rst b/NEWS.rst index 36a681e..6e5c8ff 100644 --- a/NEWS.rst +++ b/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. diff --git a/docs/language/internals.rst b/docs/language/internals.rst index a89b356..2fb29bc 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -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 ------------- diff --git a/hy/compiler.py b/hy/compiler.py index 34a9dd5..7b48822 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -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) diff --git a/hy/contrib/hy_repr.hy b/hy/contrib/hy_repr.hy index 3baaebb..a3c7631 100644 --- a/hy/contrib/hy_repr.hy +++ b/hy/contrib/hy_repr.hy @@ -144,7 +144,7 @@ (hy-repr (dict x))))) (for [[types fmt] (partition [ - list "[...]" + [list HyList] "[...]" [set HySet] "#{...}" frozenset "(frozenset #{...})" dict-keys "(dict-keys [...])" diff --git a/hy/contrib/multi.hy b/hy/contrib/multi.hy index 1a19fc9..c84adf9 100644 --- a/hy/contrib/multi.hy +++ b/hy/contrib/multi.hy @@ -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))))) diff --git a/hy/contrib/walk.hy b/hy/contrib/walk.hy index d30f29e..691645e 100644 --- a/hy/contrib/walk.hy +++ b/hy/contrib/walk.hy @@ -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]) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 1021c76..e1d5d48 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -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 - (setv g (gensym)) - `(if (do (setv ~g ~(first branch)) ~g) ~g)) - `(if ~(first branch) (do ~@(cut branch 1))))) + (or branches + (return)) - (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))) + `(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)) + [`(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))))) diff --git a/hy/models.py b/hy/models.py index fbced02..55d0b79 100644 --- a/hy/models.py +++ b/hy/models.py @@ -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())) diff --git a/tests/native_tests/contrib/walk.hy b/tests/native_tests/contrib/walk.hy index 2e59cfe..08922bf 100644 --- a/tests/native_tests/contrib/walk.hy +++ b/tests/native_tests/contrib/walk.hy @@ -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 [c 3] - ~(with* [d 4] (do)) - ~@[(with* [e 5] (do))]))))) + '(with [b 2]) + `(with [c 3] + ~(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 [] diff --git a/tests/native_tests/quote.hy b/tests/native_tests/quote.hy index 84371f0..9560bcc 100644 --- a/tests/native_tests/quote.hy +++ b/tests/native_tests/quote.hy @@ -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 [] diff --git a/tests/test_models.py b/tests/test_models.py index b9e6a90..52d4518 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -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():