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

Operators cannot be used with single-argument function shorthand in some cases #854

Open
imm opened this issue Dec 15, 2023 · 4 comments
Open

Comments

@imm
Copy link

imm commented Dec 15, 2023

It seems to me that I saw a discussion of a similar problem somewhere here a few months ago, but I couldn’t find it now and could have confused it with something, so I’ll write just in case, since I encountered this in practice.

At first I thought this only applied to the humanized NOT operator:

collection.filter ({condition}) => condition // A lot of typing
collection.filter ({condition}) => not condition // Could it be shorter?
collection.filter &.condition  // good
collection.filter !&.condition // good
collection.filter not &.condition // error

↓↓↓

collection.filter(({condition}) => condition)
collection.filter(({condition}) => !condition)
collection.filter($ => $.condition)
collection.filter($1 => !$1.condition)
collection.filter(!$2 => $2.condition) // weird

But it turned out that for binary operators this approach does not work at all (presumably):

arr.filter &.a and b    // good
arr.filter a and &.b    // bad
arr.filter a && &.b     // bad
arr.filter &.a and &.b  // bad

↓↓↓

arr.filter($ => $.a && b)
arr.filter(a && $1 => $1.b)
arr.filter(a && $2 => $2.b)
arr.filter($3 => $3.a && $4 => $4.b)
arr.map &.a + b   // good
arr.map a + &.b   // bad
arr.map &.a + &.b // bad

operator sum
arr.map &.a sum b   // good
arr.map a sum &.b   // bad
arr.map &.a sum &.b // bad

↓↓↓

arr.map($ => $.a + b)
arr.map(a + $1 => $1.b)
arr.map($2 => $2.a + $3 => $3.b)


arr.map($4 => sum($4.a, b))
arr.map(sum(a, $5 => $5.b))
arr.map($6 => sum($6.a, $7 => $7.b))

It seems to me that this should either work or not work in all cases at once.

@edemaine
Copy link
Collaborator

edemaine commented Dec 15, 2023

not & omission is a bug.

The others are as designed (so far) : the function arrow goes where the & is. Without a rule like this, you don't know where to put the arrow; your other examples are inherently ambiguous. Unless you can come up with a better rule?

See also #480 for some ideas for improvements.

@imm
Copy link
Author

imm commented Dec 15, 2023

Thanks for the link. This is exactly what I couldn't find yesterday. The subject is complex, but the feature itself, in my opinion, is very useful.

I agree that examples with binary operations are ambiguous. It seems to me that the problem is that Civet interprets this ambiguity without considering it an error. From the user's point of view, + is a commutative operation, but Civet violates this and produces working code in one case and invalid code in another:
arr.map a + &.barr.map(a + $ => $.b)
The js interpreter says it's a syntax error (malformed arrow function parameter list). How is this different from collection.filter(!$ => $.condition)? Why is the output not like this:

arr.map(a + ($ => $.b))
collection.filter(!($ => $.condition))

This is now syntactically correct code.

Some thoughts.

& as a shorthand for a function reminds me of two things I've seen: a similar shorthand in Ruby and shorthand notation for functions in the Wolfram Language.

This shorthand was not a function at all in Ruby. This was just a short way to write the code of a block in which only one method is called. Combining & with operators is not allowed. The use of & is allowed only in one strict context and could not be included in a complex expression with other operators. For some reason this is allowed in Civet. For the user this raises the question: what is & in Civet anyway? Is this really a shorthand for a function or just a macro that simply replaces & with $ => $ with automatic numbering? If it's a shorthand, what is the set of these functions? As I understand it, these are only functions with one argument that can be written as one expression, in which the argument appears only on the left (but the case of unary operators violates this definition). This limitation looks very strange. I'm saying all this from the user's point of view. I understand that technically this is not an easy task. But what is the reason for these difficulties and limitations? Maybe it's impossible to get a full set of functions using just one symbol?

It is because of the last question that I remember the short form of notation of functions in the Wolfram Language. The ampersand is used at the end of the expression, and #, #1, #2, etc. are used for arguments. If this notation were borrowed for Civet, it would look like this:

collection.filter(not #.condition &)
arr.filter(a and #.b &)
arr.map(1/# &)
arr.reduce(#1 + #2 &)

This way it would be possible to use expressions in which the argument could goes anywhere, and it would also be possible to use more than one argument (which is so missing for use in the reduce function). Markers # and & can of course be replaced with other convenient ones (e.g. & → ; and #&).

I'll think about it some more and I'd even like to try to change something in the Civet code, but I don't have as much free time for this as I'd like.

@imm
Copy link
Author

imm commented Dec 15, 2023

This shorthand was not a function at all in Ruby. This was just a short way to write the code of a block in which only one method is called. Combining & with operators is not allowed.

I wrote some nonsense here. This construct is simply a kind of method reference in Ruby. Of course, we are not talking about Ruby, but about the desire to use a short syntax for very simple functions.

@edemaine
Copy link
Collaborator

Perhaps we should continue discussion in the #693 thread, which was about this issue.

Meanwhile, #883 fixes not & and adds documentation about what the current meaning of & is.

I'm familiar with Mathematica/Wolfram notation. Personally I've found it extremely counterintuitive, especially to read but also to write (despite knowing shell scripts where & presumably comes from). It also feels pretty ambiguous in terms of where the function boundary is, so you usually need at least one set of parentheses to delineate.

& has the advantage of usually not needing parentheses; it's effectively an open bracket that spans maximally to the right. I agree that it's limited (can't re-use the argument, can't put things to the left that aren't unary opreators), but we've struggled to find a more general notation that's any more concise than ($) => ... which is already quite concise. If you come up with something that beats this and is more general than &, we'd love to hear it!

See also #85 for thoughts on re-using &.

I think there could be nice ways to handle the binary case, with the same limitations as &. Something like x.reduce &1+&2.

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

2 participants