Repository: hylang/hy

Issue:
Merge fn and lambda
With the new compiler, there's not much point in having separate fn and lambda, as anonymous function defs can be used in an expression context anyway.

The compiler should be smart enough to dispatch between ast.FunctionDef and ast.Lambda at compile time: if the body is just an expr, return a Lambda, else return a FunctionDef.

Let's do that!

Merge fn and lambda
With the new compiler, there's not much point in having separate fn and lambda, as anonymous function defs can be used in an expression context anyway.

The compiler should be smart enough to dispatch between ast.FunctionDef and ast.Lambda at compile time: if the body is just an expr, return a Lambda, else return a FunctionDef.

Let's do that!



Discussion:
Yes, please!

I claim that for tonight.

Yes, please!

I claim that for tonight.


Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -841,31 +841,6 @@ def compile_global_expression(self, expr):
                           lineno=e.start_line,
                           col_offset=e.start_column)
 
-    @builds("lambda")
-    @checkargs(2)
-    def compile_lambda_expression(self, expr):
-        expr.pop(0)
-        sig = expr.pop(0)
-        body = self.compile(expr.pop(0))
-
-        body += ast.Lambda(
-            lineno=expr.start_line,
-            col_offset=expr.start_column,
-            args=ast.arguments(args=[
-                ast.Name(arg=ast_str(x), id=ast_str(x),
-                         ctx=ast.Param(),
-                         lineno=x.start_line,
-                         col_offset=x.start_column)
-                for x in sig],
-                vararg=None,
-                kwarg=None,
-                defaults=[],
-                kwonlyargs=[],
-                kw_defaults=[]),
-            body=body.force_expr)
-
-        return body
-
     @builds("yield")
     @checkargs(max=1)
     def compile_yield_expression(self, expr):
@@ -1406,16 +1381,37 @@ def compile_list(self, expression):
                         col_offset=expression.start_column)
         return ret
 
+    @builds("lambda")
     @builds("fn")
     @checkargs(min=1)
     def compile_function_def(self, expression):
-        expression.pop(0)
-
-        name = self.get_anon_fn()
+        called_as = expression.pop(0)
 
         arglist = expression.pop(0)
         ret, args, defaults, stararg, kwargs = self._parse_lambda_list(arglist)
+
+        args = ast.arguments(
+            args=[ast.Name(arg=ast_str(x), id=ast_str(x),
+                           ctx=ast.Param(),
+                           lineno=x.start_line,
+                           col_offset=x.start_column)
+                  for x in args],
+            vararg=stararg,
+            kwarg=kwargs,
+            kwonlyargs=[],
+            kw_defaults=[],
+            defaults=defaults)
+
         body = self._compile_branch(expression)
+        if not body.stmts and called_as == "lambda":
+            ret += ast.Lambda(
+                lineno=expression.start_line,
+                col_offset=expression.start_column,
+                args=args,
+                body=body.force_expr)
+
+            return ret
+
         if body.expr:
             body += ast.Return(value=body.expr,
                                lineno=body.expr.lineno,
@@ -1425,22 +1421,12 @@ def compile_function_def(self, expression):
             body += ast.Pass(lineno=expression.start_line,
                              col_offset=expression.start_column)
 
+        name = self.get_anon_fn()
+
         ret += ast.FunctionDef(name=name,
                                lineno=expression.start_line,
                                col_offset=expression.start_column,
-                               args=ast.arguments(
-                                   args=[
-                                       ast.Name(
-                                           arg=ast_str(x), id=ast_str(x),
-                                           ctx=ast.Param(),
-                                           lineno=x.start_line,
-                                           col_offset=x.start_column)
-                                       for x in args],
-                                   vararg=stararg,
-                                   kwarg=kwargs,
-                                   kwonlyargs=[],
-                                   kw_defaults=[],
-                                   defaults=defaults),
+                               args=args,
                                body=body.stmts,
                                decorator_list=[])
 


Test patch:
diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py
--- a/tests/compilers/test_ast.py
+++ b/tests/compilers/test_ast.py
@@ -199,13 +199,13 @@ def test_ast_bad_global():
 
 def test_ast_good_lambda():
     "Make sure AST can compile valid lambda"
+    hy_compile(tokenize("(lambda [])"))
     hy_compile(tokenize("(lambda [] 1)"))
 
 
 def test_ast_bad_lambda():
     "Make sure AST can't compile invalid lambda"
     cant_compile("(lambda)")
-    cant_compile("(lambda [])")
 
 
 def test_ast_good_yield():
diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy
--- a/tests/native_tests/language.hy
+++ b/tests/native_tests/language.hy
@@ -129,7 +129,9 @@
 (defn test-lambda []
   "NATIVE: test lambda operator"
   (setv square (lambda [x] (* x x)))
-  (assert (= 4 (square 2))))
+  (assert (= 4 (square 2)))
+  (setv lambda_list (lambda [test &rest args] (, test args)))
+  (assert (= (, 1 (, 2 3)) (lambda_list 1 2 3))))
 
 
 (defn test-imported-bits []