Make fn work like lambda and remove lambda (#1228)

* with-decorator: Allow a `setv` form as the form to be decorated

This feature is of dubious value by itself, but it's necessary to allow `defn` to create a lambda instead of a `def`.

* Make `fn` work the same as `lambda`

That is, allow it to generate a `lambda` instead of a `def` statement if the function body is just an expression.

I've removed two uses of with_decorator in hy.compiler because they'd require adding another case to HyASTCompiler.compile_decorate_expression and they have no ultimate effect, anyway.

In a few tests, I've added a meaningless statement in `fn` bodies to force generation of a `def`.

I've removed `test_fn_compiler_empty_function` rather than rewrite it because it seems like a pain to maintain and not very useful.

* Remove `lambda`, now that `fn` does the same thing
This commit is contained in:
Kodi Arfer 2017-02-22 15:36:52 -08:00 committed by Ryan Gonzalez
parent 45b7a4ac9d
commit e4a7b317e1
12 changed files with 92 additions and 76 deletions

2
NEWS
View File

@ -3,9 +3,11 @@ Changes from 0.12.1
[ Language Changes ] [ Language Changes ]
* `let` has been removed. Python's scoping rules do not make a proper * `let` has been removed. Python's scoping rules do not make a proper
implementation of it possible. Use `setv` instead. implementation of it possible. Use `setv` instead.
* `lambda` has been removed, but `fn` now does exactly what `lambda` did
* Added bytestring literals, which create `bytes` objects under Python 3 * Added bytestring literals, which create `bytes` objects under Python 3
and `str` objects under Python 2 and `str` objects under Python 2
* Commas and underscores are allowed in numeric literals * Commas and underscores are allowed in numeric literals
* with-decorator: Allow a `setv` form as the form to be decorated
* xor: If exactly one argument is true, return it * xor: If exactly one argument is true, return it
[ Bug Fixes ] [ Bug Fixes ]

View File

@ -236,7 +236,7 @@ xi
Usage ``(xi body ...)`` Usage ``(xi body ...)``
Returns a function with parameters implicitly determined by the presence in the body of xi parameters. An xi symbol designates the ith parameter (1-based, e.g. x1, x2, x3, etc.), or all remaining parameters for xi itself. This is not a replacement for lambda. The xi forms cannot be nested. Returns a function with parameters implicitly determined by the presence in the body of xi parameters. An xi symbol designates the ith parameter (1-based, e.g. x1, x2, x3, etc.), or all remaining parameters for xi itself. This is not a replacement for fn. The xi forms cannot be nested.
This is similar to Clojure's anonymous function literals (``#()``). This is similar to Clojure's anonymous function literals (``#()``).

View File

@ -406,8 +406,8 @@ do
``do`` is used to evaluate each of its arguments and return the ``do`` is used to evaluate each of its arguments and return the
last one. Return values from every other than the last argument are discarded. last one. Return values from every other than the last argument are discarded.
It can be used in ``lambda`` or ``list-comp`` to perform more complex logic as It can be used in ``list-comp`` to perform more complex logic as shown in one
shown in one of the following examples. of the following examples.
Some example usage: Some example usage:
@ -1116,13 +1116,15 @@ that ``import`` can be used.
(import [sys [*]]) (import [sys [*]])
lambda / fn fn
----------- -----------
``lambda`` and ``fn`` can be used to define an anonymous function. The parameters are ``fn``, like Python's ``lambda``, can be used to define an anonymous function.
similar to ``defn``: the first parameter is vector of parameters and the rest is the Unlike Python's ``lambda``, the body of the function can comprise several
body of the function. ``lambda`` returns a new function. In the following example, an statements. The parameters are similar to ``defn``: the first parameter is
anonymous function is defined and passed to another function for filtering output. vector of parameters and the rest is the body of the function. ``fn`` returns a
new function. In the following example, an anonymous function is defined and
passed to another function for filtering output.
.. code-block:: clj .. code-block:: clj
@ -1642,6 +1644,9 @@ will be 4 (``1+1 + 1+1``).
=> (addition 1 1) => (addition 1 1)
8 8
In addition to ``defn`` forms, ``with-decorator`` can be used with ``defclass``
and ``setv`` forms. In the latter case, the generated Python code uses an
ordinary function call rather than decorator syntax.
#@ #@
~~ ~~

View File

@ -164,11 +164,11 @@ def ideas_macro():
;;; filtering a list w/ a lambda ;;; filtering a list w/ a lambda
(filter (lambda [x] (= (% x 2) 0)) (range 0 10)) (filter (fn [x] (= (% x 2) 0)) (range 0 10))
;;; swaggin' functional bits (Python rulez) ;;; swaggin' functional bits (Python rulez)
(max (map (lambda [x] (len x)) ["hi" "my" "name" "is" "paul"])) (max (map (fn [x] (len x)) ["hi" "my" "name" "is" "paul"]))
""")]) """)])

View File

@ -1417,12 +1417,28 @@ class HyASTCompiler(object):
def compile_decorate_expression(self, expr): def compile_decorate_expression(self, expr):
expr.pop(0) # with-decorator expr.pop(0) # with-decorator
fn = self.compile(expr.pop(-1)) fn = self.compile(expr.pop(-1))
if not fn.stmts or not (isinstance(fn.stmts[-1], ast.FunctionDef) or if fn.stmts and isinstance(fn.stmts[-1], (ast.FunctionDef,
isinstance(fn.stmts[-1], ast.ClassDef)): ast.ClassDef)):
raise HyTypeError(expr, "Decorated a non-function")
decorators, ret, _ = self._compile_collect(expr) decorators, ret, _ = self._compile_collect(expr)
fn.stmts[-1].decorator_list = decorators + fn.stmts[-1].decorator_list fn.stmts[-1].decorator_list = (decorators +
fn.stmts[-1].decorator_list)
return ret + fn return ret + fn
elif fn.stmts and isinstance(fn.stmts[-1], ast.Assign):
# E.g., (with-decorator foo (setv f (fn [] 5)))
# We can't use Python's decorator syntax, but we can get the
# same effect.
decorators, ret, _ = self._compile_collect(expr)
for d in decorators:
fn.stmts[-1].value = ast.Call(func=d,
args=[fn.stmts[-1].value],
keywords=[],
starargs=None,
kwargs=None,
lineno=expr.start_line,
col_offset=expr.start_column)
return fn
else:
raise HyTypeError(expr, "Decorated a non-function")
@builds("with*") @builds("with*")
@checkargs(min=2) @checkargs(min=2)
@ -2285,17 +2301,15 @@ class HyASTCompiler(object):
col_offset=expression.start_column) col_offset=expression.start_column)
return ret return ret
@builds("lambda")
@builds("fn") @builds("fn")
@checkargs(min=1) @checkargs(min=1)
def compile_function_def(self, expression): def compile_function_def(self, expression):
called_as = expression.pop(0) expression.pop(0)
arglist = expression.pop(0) arglist = expression.pop(0)
if not isinstance(arglist, HyList): if not isinstance(arglist, HyList):
raise HyTypeError(expression, raise HyTypeError(expression,
"First argument to `{}' must be a list".format( "First argument to `fn' must be a list")
called_as))
(ret, args, defaults, stararg, (ret, args, defaults, stararg,
kwonlyargs, kwonlydefaults, kwargs) = self._parse_lambda_list(arglist) kwonlyargs, kwonlydefaults, kwargs) = self._parse_lambda_list(arglist)
@ -2367,7 +2381,7 @@ class HyASTCompiler(object):
defaults=defaults) defaults=defaults)
body = self._compile_branch(expression) body = self._compile_branch(expression)
if not body.stmts and called_as == "lambda": if not body.stmts:
ret += ast.Lambda( ret += ast.Lambda(
lineno=expression.start_line, lineno=expression.start_line,
col_offset=expression.start_column, col_offset=expression.start_column,
@ -2513,7 +2527,6 @@ class HyASTCompiler(object):
if kw in expression[0]: if kw in expression[0]:
raise HyTypeError(name, "macros cannot use %s" % kw) raise HyTypeError(name, "macros cannot use %s" % kw)
new_expression = HyExpression([ new_expression = HyExpression([
HySymbol("with_decorator"),
HyExpression([HySymbol("hy.macros.macro"), name]), HyExpression([HySymbol("hy.macros.macro"), name]),
HyExpression([HySymbol("fn")] + expression), HyExpression([HySymbol("fn")] + expression),
]).replace(expression) ]).replace(expression)
@ -2536,7 +2549,6 @@ class HyASTCompiler(object):
"for reader macro name" % type(name).__name__)) "for reader macro name" % type(name).__name__))
name = HyString(name).replace(name) name = HyString(name).replace(name)
new_expression = HyExpression([ new_expression = HyExpression([
HySymbol("with_decorator"),
HyExpression([HySymbol("hy.macros.reader"), name]), HyExpression([HySymbol("hy.macros.reader"), name]),
HyExpression([HySymbol("fn")] + expression), HyExpression([HySymbol("fn")] + expression),
]).replace(expression) ]).replace(expression)

View File

@ -65,7 +65,7 @@
"Helper for shadow comparison operators" "Helper for shadow comparison operators"
(if (< (len args) 2) (if (< (len args) 2)
(raise (TypeError "Need at least 2 arguments to compare")) (raise (TypeError "Need at least 2 arguments to compare"))
(reduce (lambda [x y] (and x y)) (reduce (fn [x y] (and x y))
(list-comp (op x y) (list-comp (op x y)
[(, x y) (zip args (cut args 1))])))) [(, x y) (zip args (cut args 1))]))))
(defn < [&rest args] (defn < [&rest args]

View File

@ -135,9 +135,9 @@
"Returns a function with parameters implicitly determined by the presence in "Returns a function with parameters implicitly determined by the presence in
the body of xi parameters. An xi symbol designates the ith parameter the body of xi parameters. An xi symbol designates the ith parameter
(1-based, e.g. x1, x2, x3, etc.), or all remaining parameters for xi itself. (1-based, e.g. x1, x2, x3, etc.), or all remaining parameters for xi itself.
This is not a replacement for lambda. The xi forms cannot be nested. " This is not a replacement for fn. The xi forms cannot be nested. "
(setv flatbody (flatten body)) (setv flatbody (flatten body))
`(lambda [;; generate all xi symbols up to the maximum found in body `(fn [;; generate all xi symbols up to the maximum found in body
~@(genexpr (HySymbol (+ "x" ~@(genexpr (HySymbol (+ "x"
(str i))) (str i)))
[i (range 1 [i (range 1

View File

@ -225,13 +225,13 @@ def test_ast_bad_defclass():
def test_ast_good_lambda(): def test_ast_good_lambda():
"Make sure AST can compile valid lambda" "Make sure AST can compile valid lambda"
can_compile("(lambda [])") can_compile("(fn [])")
can_compile("(lambda [] 1)") can_compile("(fn [] 1)")
def test_ast_bad_lambda(): def test_ast_bad_lambda():
"Make sure AST can't compile invalid lambda" "Make sure AST can't compile invalid lambda"
cant_compile("(lambda)") cant_compile("(fn)")
def test_ast_good_yield(): def test_ast_good_yield():
@ -369,9 +369,11 @@ def test_ast_expression_basics():
def test_ast_anon_fns_basics(): def test_ast_anon_fns_basics():
""" Ensure anon fns work. """ """ Ensure anon fns work. """
code = can_compile("(fn (x) (* x x))").body[0] code = can_compile("(fn (x) (* x x))").body[0].value
assert type(code) == ast.Lambda
code = can_compile("(fn (x) (print \"multiform\") (* x x))").body[0]
assert type(code) == ast.FunctionDef assert type(code) == ast.FunctionDef
code = can_compile("(fn (x))").body[0] can_compile("(fn (x))")
cant_compile("(fn)") cant_compile("(fn)")
@ -430,7 +432,7 @@ def test_lambda_list_keywords_kwargs():
def test_lambda_list_keywords_kwonly(): def test_lambda_list_keywords_kwonly():
"""Ensure we can compile functions with &kwonly if we're on Python """Ensure we can compile functions with &kwonly if we're on Python
3, or fail with an informative message on Python 2.""" 3, or fail with an informative message on Python 2."""
kwonly_demo = "(fn [&kwonly a [b 2]] (print a b))" kwonly_demo = "(fn [&kwonly a [b 2]] (print 1) (print a b))"
if PY3: if PY3:
code = can_compile(kwonly_demo) code = can_compile(kwonly_demo)
for i, kwonlyarg_name in enumerate(('a', 'b')): for i, kwonlyarg_name in enumerate(('a', 'b')):

View File

@ -57,24 +57,6 @@ class HyASTCompilerTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.c = compiler.HyASTCompiler('test') self.c = compiler.HyASTCompiler('test')
def test_fn_compiler_empty_function(self):
ret = self.c.compile_function_def(
self._make_expression(HySymbol("fn"), HyList()))
self.assertEqual(ret.imports, {})
self.assertEqual(len(ret.stmts), 1)
stmt = ret.stmts[0]
self.assertIsInstance(stmt, ast.FunctionDef)
self.assertIsInstance(stmt.args, ast.arguments)
self.assertEqual(stmt.args.vararg, None)
self.assertEqual(stmt.args.kwarg, None)
self.assertEqual(stmt.args.defaults, [])
self.assertEqual(stmt.decorator_list, [])
self.assertEqual(len(stmt.body), 1)
self.assertIsInstance(stmt.body[0], ast.Pass)
self.assertIsInstance(ret.expr, ast.Name)
def test_compiler_bare_names(self): def test_compiler_bare_names(self):
""" """
Check that the compiler doesn't drop bare names from code branches Check that the compiler doesn't drop bare names from code branches

View File

@ -12,7 +12,8 @@ def test_basics():
def test_stringer(): def test_stringer():
"Make sure the basics of the importer work" "Make sure the basics of the importer work"
_ast = import_buffer_to_ast("(defn square [x] (* x x))", '') _ast = import_buffer_to_ast(
"(defn square [x] (print \"hello\") (* x x))", '')
assert type(_ast.body[0]) == ast.FunctionDef assert type(_ast.body[0]) == ast.FunctionDef

View File

@ -75,7 +75,7 @@
(except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e))))) (except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
(try (eval '(defn get [] (print "hello"))) (try (eval '(defn get [] (print "hello")))
(except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e))))) (except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
(try (eval '(defn lambda [] (print "hello"))) (try (eval '(defn fn [] (print "hello")))
(except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))) (except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e))))))
@ -133,10 +133,6 @@
(defn test-alias-names-in-errors [] (defn test-alias-names-in-errors []
"NATIVE: tests that native aliases show the correct names in errors" "NATIVE: tests that native aliases show the correct names in errors"
(try (eval '(lambda))
(except [e [Exception]] (assert (in "lambda" (str e)))))
(try (eval '(fn))
(except [e [Exception]] (assert (in "fn" (str e)))))
(try (eval '(setv 1 2 3)) (try (eval '(setv 1 2 3))
(except [e [Exception]] (assert (in "setv" (str e))))) (except [e [Exception]] (assert (in "setv" (str e)))))
(try (eval '(def 1 2 3)) (try (eval '(def 1 2 3))
@ -343,11 +339,11 @@
"level"))) "level")))
(defn test-lambda [] (defn test-fn []
"NATIVE: test lambda operator" "NATIVE: test fn operator"
(setv square (lambda [x] (* x x))) (setv square (fn [x] (* x x)))
(assert (= 4 (square 2))) (assert (= 4 (square 2)))
(setv lambda_list (lambda [test &rest args] (, test args))) (setv lambda_list (fn [test &rest args] (, test args)))
(assert (= (, 1 (, 2 3)) (lambda_list 1 2 3)))) (assert (= (, 1 (, 2 3)) (lambda_list 1 2 3))))
@ -1424,7 +1420,7 @@
(defmacro identify-keywords [&rest elts] (defmacro identify-keywords [&rest elts]
`(list `(list
(map (map
(lambda (x) (if (is-keyword x) "keyword" "other")) (fn (x) (if (is-keyword x) "keyword" "other"))
~elts))) ~elts)))
(defn test-keywords-and-macros [] (defn test-keywords-and-macros []

View File

@ -1,19 +1,40 @@
(defn foodec [func] (defn test-decorated-1line-function []
(lambda [] (+ 1 1))) (defn foodec [func]
(fn [] (+ (func) 1)))
(with-decorator foodec
(with-decorator foodec
(defn tfunction [] (defn tfunction []
(* 2 2))) (* 2 2)))
(assert (= (tfunction) 5)))
(defn bardec [cls] (defn test-decorated-multiline-function []
(setv cls.my_attr 123) (defn bazdec [func]
(fn [] (+ (func) "x")))
(with-decorator bazdec
(defn f []
(setv intermediate "i")
(+ intermediate "b")))
(assert (= (f) "ibx")))
(defn test-decorated-class []
(defn bardec [cls]
(setv cls.attr2 456)
cls) cls)
(with-decorator bardec
(with-decorator bardec
(defclass cls [] (defclass cls []
[my_attr 456])) [attr1 123]))
(assert (= cls.attr1 123))
(assert (= cls.attr2 456)))
(defn test-decorated-setv []
(defn d [func]
(fn [] (+ (func) "z")))
(with-decorator d
(setv f (fn [] "hello")))
(assert (= (f) "helloz")))
(defn test-decorator-clobbing [] (defn test-decorator-clobbing []
"NATIVE: Tests whether nested decorators work" "NATIVE: Tests whether nested decorators work"
@ -24,8 +45,3 @@
(with-decorator dec2 (with-decorator dec2
(defn f [] 1))) (defn f [] 1)))
(assert (= (f) 4)))) (assert (= (f) 4))))
(defn test-decorators []
"NATIVE: test decorators."
(assert (= (tfunction) 2))
(assert (= cls.my_attr 123)))