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:
parent
45b7a4ac9d
commit
e4a7b317e1
2
NEWS
2
NEWS
@ -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 ]
|
||||||
|
@ -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 (``#()``).
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#@
|
#@
|
||||||
~~
|
~~
|
||||||
|
@ -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"]))
|
||||||
|
|
||||||
""")])
|
""")])
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
@ -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')):
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 []
|
||||||
|
@ -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)))
|
|
||||||
|
Loading…
Reference in New Issue
Block a user