Skip to content

Deprecate lambda arrow (->) and replace it with LAMBDA x : x + 1 - #17235

Merged
Mytherin merged 15 commits into
duckdb:mainfrom
taniabogatsch:lambda-arrow
May 7, 2025
Merged

Mytherin merged 15 commits into
duckdb:mainfrom
taniabogatsch:lambda-arrow

Conversation

@taniabogatsch

@taniabogatsch taniabogatsch commented Apr 24, 2025 •

Copy link
Copy Markdown
Member

Changes

Currently, one way to specify lambda functions is by using the arrow operator syntax: x -> x + 1

This PR deprecates that syntax and replaces it with Python-style lambda syntax: lambda x: x + 1

You can enable the old behavior with: SET allow_deprecated_lambda_syntax=true (default value is also true).
On default, it is currently still possible to use both the old and the new syntax.

Timeline

Starting with this PR / 1.3.0, we introduce the new syntax, and the allow_deprecated_lambda_syntax setting.

Starting with 1.4.0, we intend to print some kind of warning when using the old behavior and allow_deprecated_lambda_syntax is on the default setting.

Starting with 1.5.0, we intend to default allow_deprecated_lambda_syntax to no allowed.

Starting with 1.6.0, we'll disable using the deprecated lambda syntax, and remove the setting.

Motivation

The same arrow operator is also used in the JSON extension. The decision to deprecate it for lambda functions stems from many issues that arise from using the same operator for both cases. Binding this operator is a lot of trouble, as we must resolve ambiguity conflicts and precedence issues. The operator precedence that is necessary for the lambda arrow to work as expected causes weird behavior for the JSON arrow operator (-> and ->>).

Since Postgres uses the arrow operator for JSON operations, we modified the lambda functions so that the arrow operator can no longer be used to reconcile this problem.

@taniabogatsch

Copy link
Copy Markdown
Member Author

Related issue: https://github.com/duckdblabs/duckdb-internal/issues/4524.

I've duplicated many old lambda tests in a subdirectory and enabled allow_deprecated_lambda_syntax. I've also updated the original tests to use the new syntax to find and solve any issues with it.

@duckdb-draftbot
duckdb-draftbot marked this pull request as draft April 24, 2025 12:11
@taniabogatsch
taniabogatsch marked this pull request as ready for review April 24, 2025 12:11

@lnkuiper lnkuiper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! The changes seem good to me, we discussed these already. I like that you have a very descriptive error message.

I was also not expecting you to fully change all tests already! Looks great, thanks!

Looking forward to v1.4.0 when we can fully deprecate this and allow proper arrow precedence for JSON.

@Tishj

Tishj commented Apr 24, 2025

Copy link
Copy Markdown
Member

Great change, great work 👍

@carlopi

carlopi commented Apr 24, 2025 •

Copy link
Copy Markdown
Member

I would add some tests around mixing syntaxes AND around macro definitions (since those are not parser time but bind time).

Like:

CREATE OR REPLACE function transpose(lst) AS (
      SELECT list_transform(range(1, 1 + length(lst[1])),
          j -> list_transform(range(1, length(lst) + 1),
             LAMBDA i : lst[i][j]
          )
      )
  );
SELECT transpose('123');

(currently INTERNAL Error)

Plus there is also some possible problem with serialization, like:

carlo@ScroogeMcDuck duckdb % ./build/release/duckdb tmp_file.db
v1.3.0-dev2747 c1bc6aa827
Enter ".help" for usage hints.
D CREATE OR REPLACE function transpose(l) AS (
          SELECT list_transform(asd:=range(1, 1 + length(l[1])),
            i := j ->list_transform(range(1, length(l) + 1),
             l :=    i-> l[i:12]
              )
          )
      );
D 
D 
carlo@ScroogeMcDuck duckdb % duckdb tmp_file.db                
Error: unable to open database "tmp_file.db": Serialization Error: Failed to deserialize: expected end of object, but found field id: 202
carlo@ScroogeMcDuck duckdb % ./build/release/duckdb tmp_file.db
v1.3.0-dev2747 c1bc6aa827
Enter ".help" for usage hints.

(unsure what's the path here, does this need to be actually visible to storage serialization?)

And I would say : is somehow loaded already:

WITH tbl AS (SELECT l :'lambda')
  FROM tbl
      SELECT list_transform(range(1, length(l) + 1), a := lambda i : l[0:i]);

@eaubin

eaubin commented Apr 24, 2025

Copy link
Copy Markdown

This makes me a tiny bit sad, I liked the -> syntax and found it more readable and less esoteric than lambda. On the other hand, I'm excited for more reliable binding with macros. Do you know if this fixes this discussion?

@taniabogatsch

Copy link
Copy Markdown
Member Author

@carlopi, the output of this one looks correct to me - I am not sure what the a := ... does, there? Is that a named parameter wrapping the expression?

WITH tbl AS (SELECT l :'lambda')
  FROM tbl
      SELECT list_transform(range(1, length(l) + 1), a := lambda i : l[0:i]);

@taniabogatsch

Copy link
Copy Markdown
Member Author

(unsure what's the path here, does this need to be actually visible to storage serialization?)

If we don't expose it, then we'll get different results for the verification runs.

@lnkuiper

Copy link
Copy Markdown
Member

@taniabogatsch a := is a named parameter, we use that syntax for named parameters in macros. I didn't realise it also works positionally for scalar functions that do not have any named parameters. It just gives the value the alias "a" internally.

@duckdb-draftbot
duckdb-draftbot marked this pull request as draft April 25, 2025 12:53
@taniabogatsch

Copy link
Copy Markdown
Member Author

@carlopi, I added some changes and all of these should pass now. Thanks a lot for your detailed comments!

statement error
CREATE OR REPLACE function transpose(lst) AS (
	SELECT list_transform(range(1, 1 + length(lst[1])),
		j -> list_transform(range(1, length(lst) + 1),
			LAMBDA i : lst[i][j]
		)
	)
);
----
<REGEX>:Binder Error.*Deprecated lambda arrow.*

statement error
CREATE OR REPLACE FUNCTION transpose(lst) AS (
	SELECT list_transform(range(1, 1 + length(lst[1])),
		j -> list_transform(range(1, length(lst) + 1),
			i -> lst[i][j]
		)
	)
);
----
<REGEX>:Binder Error.*Deprecated lambda arrow.*

statement ok
SET allow_deprecated_lambda_syntax=true

statement ok
CREATE OR REPLACE FUNCTION transpose(lst) AS (
	SELECT list_transform(range(1, 1 + length(lst[1])),
		j -> list_transform(range(1, length(lst) + 1),
			i -> lst[i][j]
		)
	)
);


statement ok
ATTACH '__TEST_DIR__/deprecated_syntax.db';

statement ok
CREATE OR REPLACE FUNCTION deprecated_syntax.transpose(lst) AS (
	SELECT list_transform(range(1, 1 + length(lst[1])),
		j -> list_transform(range(1, length(lst) + 1),
			i -> lst[i][j]
		)
	)
);

statement ok
DETACH deprecated_syntax;

statement ok
ATTACH '__TEST_DIR__/deprecated_syntax.db';

@taniabogatsch
taniabogatsch marked this pull request as ready for review April 25, 2025 12:54
@taniabogatsch

Copy link
Copy Markdown
Member Author

@eaubin - I haven't looked into that discussion, yet. But I am planning to fix the remaining open issues around lambda binding after this has been merged.

@duckdb-draftbot
duckdb-draftbot marked this pull request as draft April 25, 2025 14:57
@taniabogatsch

Copy link
Copy Markdown
Member Author

@hmeriann, I adjusted the regression tests to have the new syntax. From my understanding, they will break for this PR, and once merged, work again?

@taniabogatsch
taniabogatsch marked this pull request as ready for review April 25, 2025 15:00
@ZuleykhaPavlichenkova-TomTom

Copy link
Copy Markdown
Contributor

@taniabogatsch yes, this sounds right! Thank you for letting me know!

@carlopi

carlopi commented Apr 25, 2025

Copy link
Copy Markdown
Member

Great on the bind-time checks!!

If we don't expose it, then we'll get different results for the verification runs.

I am not sure I understand, I was thinking that if this is checked at creation, then stuff should just work from there (either 1 more or both are allowed, but downstream it does not matter and whatever entered the system has to keep working).

To me exposing this to serialization adds complexity for future us /adds failures mode I am not really sure we are interested in.

I am not sure of the details, could you expand on what fails differently?
I am not sure of the details, but I think cutting storage layer would be great here.

@taniabogatsch

Copy link
Copy Markdown
Member Author

this is checked at creation, then stuff should just work from there

It is checked during the binding phase, which is after we've created a ParsedExpression with the Transformer.
I could move this check into the Transformer by propagating allow_deprecated_lambda_syntax to the ParserOptions. But then we still have the problem that once we allow both the new and old syntax, we have (if we drop the serialisation of the deprecated boolean) no way to say which one it was after serialisation/when calling ToString().

Currently, the deprecated flag lives in the LambdaExpression. If we do not add it to the serialisation code, then we need to decide on a default value. Which means that a deprecated expression can become a non-deprecated expression, and the other way around.

And if we decide to not have the deprecated flag in the LambdaExpression at all (which would solve both serialisation and default value), then I don't know how to solve ToString():

string LambdaExpression::ToString() const {
	if (deprecated) {
		return "(" + lhs->ToString() + " -> " + expr->ToString() + ")";
	}
	return "(LAMBDA " + lhs->ToString() + " : " + expr->ToString() + ")";
}

Because depending on which representation we choose, we either 1. start throwing invalid syntax for originally valid expressions, or turn a JSON arrow into a lambda expression...

@Mytherin

Mytherin commented May 1, 2025

Copy link
Copy Markdown
Member

Yes that will work as well (spaces are nops in SQL). @taniabogatsch maybe we can update the tests to follow the syntax commonly used in Python instead? (e.g. lambda x: x + 1 - no caps and no space before the colon).

@taniabogatsch
taniabogatsch marked this pull request as ready for review May 2, 2025 14:44
@taniabogatsch

Copy link
Copy Markdown
Member Author

I've implemented all suggestions and review requests, so this is ready for review again.
I'll adjust the PR description shortly.

@duckdb-draftbot
duckdb-draftbot marked this pull request as draft May 7, 2025 08:58
@taniabogatsch
taniabogatsch marked this pull request as ready for review May 7, 2025 09:24
@duckdb-draftbot
duckdb-draftbot marked this pull request as draft May 7, 2025 10:46
@taniabogatsch
taniabogatsch marked this pull request as ready for review May 7, 2025 10:46
@taniabogatsch

Copy link
Copy Markdown
Member Author

The regression failure is expected, and the Python failure seems unrelated.

@Mytherin
Mytherin merged commit 95b70e2 into duckdb:main May 7, 2025
@Mytherin

Mytherin commented May 7, 2025

Copy link
Copy Markdown
Member

Thanks!

@taniabogatsch
taniabogatsch deleted the lambda-arrow branch May 8, 2025 09:13
@ZuleykhaPavlichenkova-TomTom

Copy link
Copy Markdown
Contributor

Nightly Regression had failed with syntax error - it should go away tomorrow

krlmlr added a commit to duckdb/duckdb-r that referenced this pull request May 18, 2025
Deprecate lambda arrow (->) and replace it with LAMBDA x : x + 1 (duckdb/duckdb#17235)
krlmlr added a commit to duckdb/duckdb-r that referenced this pull request May 18, 2025
Deprecate lambda arrow (->) and replace it with LAMBDA x : x + 1 (duckdb/duckdb#17235)
krlmlr added a commit to duckdb/duckdb-r that referenced this pull request May 19, 2025
Deprecate lambda arrow (->) and replace it with LAMBDA x : x + 1 (duckdb/duckdb#17235)
krlmlr added a commit to duckdb/duckdb-r that referenced this pull request May 19, 2025
Deprecate lambda arrow (->) and replace it with LAMBDA x : x + 1 (duckdb/duckdb#17235)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs Documentation Use for issues or PRs that require changes in the documentation Ready To Merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants