brimdata / zed

A novel data lake based on super-structured data
https://zed.brimdata.io/
BSD 3-Clause "New" or "Revised" License
1.34k stars 67 forks source link

Make the body of a user-defined op a fully-fledged scope #5073

Closed philrz closed 3 months ago

philrz commented 3 months ago

tl;dr

In the Zed docs about statements several times we explain scopes as:

...the main scope at the start of a Zed program or a lateral scope defined by an over operator

However, there are valid reasons why a user might want to treat other ( )-enclosed code blocks (e.g, the body of a user-defined operator) like scopes.

Details

Repro is with Zed commit f1be6a4.

This topic was kicked off by a community user in a Slack thread. In their own words:

  1. Can i define a function inline?

There was some back & forth about what was implied by the word "inline" here (e.g., in language contexts it often refers to this kind of concept from C++, which indeed did not seem to be what this user had in mind). I ultimately read back this simplified example that the user confirmed captured the essence of what they had in mind.

This works:

$ cat works.zed 
func compute(num): (
  num * num
)

op square(val): (
  yield compute(val)
)

$ zq -version
Version: v1.14.0-17-gf1be6a4a

$ echo '2' | zq -I works.zed 'square(this)' -
4

However, this currently does not, as it produces a syntax error.

$ cat attempt.zed 
op square(val): (
  func compute(num): (
    num * num
  )
  yield compute(val)
)

$ echo '2' | zq -I attempt.zed 'square(this)' -
zq: error parsing Zed in attempt.zed at line 2, column 20:
  func compute(num): (
               === ^ ===

In terms of why the user might find the second approach desirable, they confirmed:

The main problem is not wanting to pollute the global namespace.

Indeed, other languages have this concept of being able to define functions, constants, etc., that are local to the innermost scope, so it's understandable why users might hope to see it in Zed.

That covers a specific case of a func inside of an op, but if we want to approach this in a general way, it seems there's other variations we might want to cover, e.g., #5075 tracks the equivalent for user-defined functions.

philrz commented 3 months ago

Verified in Zed commit 18b3c7f.

Our Zed program with the user-defined function defined within the scope of the user-defined operator now parses successfully and produces the expected result.

$ zq -version
Version: v1.14.0-19-g18b3c7f8

$ cat attempt.zed 
op square(val): (
  func compute(num): (
    num * num
  )
  yield compute(val)
)

$ echo '2' | zq -I attempt.zed 'square(this)' -
4

Thanks @mattnibs!

iloveitaly commented 3 months ago

Amazing! Thank you for implementing this!