Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nicer syntax for higher level functions #1002

Open
dasch opened this issue Jun 1, 2022 · 7 comments
Open

Nicer syntax for higher level functions #1002

dasch opened this issue Jun 1, 2022 · 7 comments

Comments

@dasch
Copy link

dasch commented Jun 1, 2022

I love the pure functional nature of Jsonnet! However, when trying to do things in a functional style, the syntax gets in the way a bit (not a lot, but a bit).

For example, defining higher level functions is a bit clumsy:

local f = function(x) function(y) function(z) z*y*z;

I think such functions would be more natural to use, and therefore get used more often, with more succinct syntax – and there's no need to invent anything fancy, since lots of other languages already use either a "fat" or a "thin" arrow, => or ->.

So the above snippet would become:

local f = x -> y -> z -> z*y*z;

This would also be nice and eligible for inline uses, e.g. std.filter(thing -> thing.isActive, things).

Hoping y'all think it makes sense! 🤞

@sparkprime
Copy link
Contributor

We probably want to use the backslash syntax for this because it's easy to parse but it's worth checking what other languages do and trying to be consistent with those if possible.

@CertainLach
Copy link
Contributor

Both backslash and -> syntax is not compatible with default arguments and deconstruction (Which is proposed here: #307, yet not implemented in neither Golang nor C++ implementations)
What about JS-like? After all, Jsonnet is based on Json, and Json is a subset of JS

std.filter((thing) => thing.isActive, things)

It is easy to parse too (The only grammar conflict i see is parened identifier)

As for currying, what about doing it on a call-site?

I.e

local multiply(x, y) = x * y;
local multiply_2 = multiply(2, ...);

multiply(2) == 4

... as last argument will create function with fixed arguments, yet allowing to set more

For calls it should look like this:

local curried = fn(1, 2, named1 = 1, named2 = 2);

curried(3, 4, named3 = 6, named4 = 7) 
// should work same as
fn(1, 2, 3, 4, named1 = 1, named2 = 2, named3 = 6, named4 = 7)

@dasch
Copy link
Author

dasch commented Jun 1, 2022

Both backslash and -> syntax is not compatible with default arguments and deconstruction (Which is proposed here: #307, yet not implemented in neither Golang nor C++ implementations)

Why would it not be compatible? Optional parentheses could still be used when necessary, e.g. local f = (x=42) -> x*2. For deconstruction, you could still have e.g. { x } -> x*2. This is not really different from how these things work in e.g. Elm or other ML based languages. The arrow would just be an inline operator.

What about JS-like? After all, Jsonnet is based on Json, and Json is a subset of JS

std.filter((thing) => thing.isActive, things)

It is easy to parse too (The only grammar conflict i see is parened identifier)

Yup, that could also be a valid syntax. I don't think I understand the grammar conflict – if the arrow has the right precedence, then the semantics should be clear cut, right? The parse tree in your example would be:

  • std.filter
    • =>
      • thing
      • thing.isActive
    • things

As for currying, what about doing it on a call-site?

I.e

local multiply(x, y) = x * y;
local multiply_2 = multiply(2, ...);

multiply(2) == 4

... as last argument will create function with fixed arguments, yet allowing to set more

For calls it should look like this:

local curried = fn(1, 2, named1 = 1, named2 = 2);

curried(3, 4, named3 = 6, named4 = 7) 
// should work same as
fn(1, 2, 3, 4, named1 = 1, named2 = 2, named3 = 6, named4 = 7)

That looks very different from what I've personally seen elsewhere – higher level functions is pretty well established as a concept.

@CertainLach
Copy link
Contributor

Optional parentheses could still be used when necessary

I think it should be treated not as optional parentheses, but as optional parenthesis omission

JS allows it too in case of single identifier, i.e (arg) => value can be written as arg => value, but not for deconstruction/multiple arguments:

([arr1, arr2]) => value
({field1, field2}) => value
(arg1, arg2) => value

Because it will make parsing much harder, and will require backtracing

@dasch
Copy link
Author

dasch commented Jun 2, 2022

@CertainLach yup, that makes sense. Again, my ML thinking leads me to think of "multiple arguments" as just a single value – a tuple. Indeed, one possible input to the deconstruction discussion would be to introduce the notion of tuples, with pattern matching allowing for setting defaults, e.g.

local (a, b, c=42) = someFunc(x, y, z);

// Symmetrical with
local f = (a, b, c=42) -> ...;
f someFunc();

That last part would perhaps be a breaking change, though – not sure if we can clearly distinguish between the a argument to f being assigned the tuple vs. deconstructing.

@CertainLach
Copy link
Contributor

Unfortunately, parameters in jsonnet isn't just a tuple deconstruction

They have non-trivial semantics regarding to named arguments (Jsonnet supports pythonic keyword arguments, (function(a, b, c) null)(1, c = 3, b = 2)) and bindings (Parameters may default to other parameters in arbitrary order, function(a = c, b = a, c = b) null)

@sbarzowski
Copy link
Collaborator

Related discussion:
#612
#223

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants