gracelang / language

Design of the Grace language and its libraries
GNU General Public License v2.0
6 stars 1 forks source link

Operator for Negating a Pattern #170

Open apblack opened 5 years ago

apblack commented 5 years ago

The new semantics of match(_)case(_)… creates a need to be able to negate patterns, so that a case guard can include a conjunction that is the negation of some other guard.

The question that arrises is: what operator should we use for this?

My initial thought was to use !, but that is ambiguous with the use of ! for negating Booleans. Since Strings and Numbers are patterns, ! "Hello" and ! 57 would become legal, being patterns that match anything other than "Hello" and anything other than 57.

For disjunctions and conjunctions of patterns we use | and &, rather than || and &&: I don't recall why, but I guess that it's to avoid exactly this confusion. This suggests using a different operator for pattern negation.

In the interim, I'm using ¬, which I know that @kjx will like, because, although ¬ has never been part of ASCII, it has always been part of EBCDIC, the coding used by real computers.

kjx commented 5 years ago

disjunctions and conjunctions of patterns we use | and &, rather than || and &&: I don't recall why

We needed to distinguish between e.g. true || false - the Boolean true --- and true | false - a pattern than matches both true and false.

! ...ambiguous with the use of ! for negating Booleans ... I'm using ¬

yep, we now have ! true which matches whatever false matches, and ¬ true which matches everything except true.

Negation. Default clauses. Confusing & probably inefficient semantics. Remind me why this is good again? I guess there's a paper looking at whether a JIT can actually speed things up somehow.

apblack commented 5 years ago

Remind me why this is good again?

That depends what you mean by "this".

  1. If "this" is a run-time type test, then it's good for writing inspectors.

  2. if "this" is making sure that exactly one case of a match(_)case(_)... is true, then it's good for revealing bugs in your program. In particular, if the two cases are types that can't be distinguished at runtime because they differ only at level n > 1, it's better to get an error than to have the program evaluate the "wrong" case.

  3. If "this" is the match(_)case(_)... construct itself, then it's good for showing students that programming patterns that use messages and dispatch rather than match(_)case(_)... are simpler and more maintainable (Alec Sharp's "Tell, don't Ask" rule).

apblack commented 1 year ago

Given that ¬ has been in use for 3½ years now, should we close this issue?

kjx commented 1 year ago

Argh. We HATES it ALL!

I think only one person has probably used that (and I till don't know how to type it).

perhaps neg(pattern) is better - yes it's more clumsy, but this is pretty rare anyway

Really we should go with & | ! for one usecase, and&& || !!for the other (after nearly suggesting & | !! for patterns and && || ! for booleans)

part of me now wants to write a proper long survey paper looking at operators in all major pls...

https://en.wikipedia.org/wiki/Operator_(computer_programming)#Operator_features_in_programming_languages

apblack commented 1 year ago

On Nov 29, 2022, at 02:10, kjx @.***> wrote:

perhaps neg(pattern) is better - yes it's more clumsy, but this is pretty rare anyway

I think that you mean pattern.neg. Or should we do the “Whole Haskell“ and abandon objects altogether?

The problem with all of these suggestions is remembering which operators apply to booleans, and which to patterns. Does && combine patterns or Booleans, and how am I supposed to remember?

If we believe that Grace is for novices, and that novices are under 30, and also that it’s just old farts like me who can’t remember things, then maybe this doesn’t matter? Not having programmed in Grace for a while now, I think that it matters a lot.

It seems to me that the straightforward solution is to eliminate the pun by which 5 and true are both constants and patterns. We should replace it by an explicit operation, such as prefix #.

Then we can use the same operation for boolean & and pattern &.

KimBruce commented 1 year ago

I find constants as patterns confusing, so some sort of explicit operation to convert would be good.

kjx commented 1 year ago

I think that you mean pattern.neg. Or should we do the “Whole Haskell“ and abandon objects altogether?

I don't mind - for a pattern of any complexity, you'll have to parenthesize either way

(1 | 2 | 3).neg vs ! (1 | 2 | 3) vs neg(1 | 2 | 3)

In Dafny I tend to define not(e) as a synonym for !e because often I've overlooked the "!"

The problem with all of these suggestions is remembering which operators apply to booleans, and which to patterns. Does && combine patterns or Booleans, snd how am I supposed to remember?

yeah I know.

there are answers. many of them are evil

If we believe that Grace is for novices, and that novices are under 30, and also that it’s just old farts like me who can’t remember things, then maybe this doesn’t matter? Not having programmed in Grace for a while now, I think that it matters a lot.

I matters, and my hypothesis that familiarly reading syntax is important (I still can't read Prolog - or Scala! - many many people can't read Smalltalk or Prolog)

unfortuantely the C operators aren't consistent && || !

It seems to me that the straightforward solution is to eliminate the pun by which 5 and true are both constants and patterns. We should replace it by an explicit operation, such as prefix #.

I'm sure we considered this, I think Michael did this for some thing (and he probably still has informed opinions, not sure if he still has interest)

the problem is how horrible does this look

match (c) case { 0 | 2 | 4 | 6 | 7 -> "even" } case { 1 | 3 | 5 | 7 | 9 -> "odd" )

compared with:

match (c) case { $0 | $2 | $4 | $6 | $8 -> "even" } case { $1 | $3 | $5 | $7 | $9 -> "odd" )

the problem with || vs | is only for Booleans, isnt it? we could stop people using Booleans as patterns - or at least without unquoting.

plus I shamelessly prefer prefix$. I don't know why.

Then we can use the same operation for boolean & and pattern &.

my heads hurt already.

I find constants as patterns confusing, so some sort of explicit operation to convert would be good.

they're not constants: they're "value objects" or something

apblack commented 1 year ago

kjx wrote:

the problem with || vs | is only for Booleans, isnt it? we could stop people using Booleans as patterns - or at least without unquoting.

Yes, you are right: the ambiguity with and, or and not applies only to Booleans. It's not a problem with Numbers, because (in Grace) 1 | 2 is only meaningful if 1 is a pattern, not if it's a Number. (Of course, novices might write 1 & 2 in place of 1 + 2, and would be confused by the answer ...)

So, one solution is to keep the pattern "pun" for Numbers and Strings, and remove it for Booleans. We could write #true and #false for the patterns matching true and false, or simply have named constants such as isTrue and isFalse declared in a dialect. (I like # as the "make a pattern" operator, because the # glyph is suggestive of a pattern ... in fact, it makes a pattern on the page. But we could also use a method .asPattern, as in true.asPattern). In practice, matching on Boolean patterns is pretty rare, since the if(_)then(_)lelse(_) method is available for discriminating on true and false, so I think that making boolean pattern matches a bit uglier is OK.

Then we have to decide if we want to keep && and || and ! for the Boolean operators. These are all, I believe, inventions of Kernighan and Ritchie, and I've never liked any of them. Using the symbol ! for not is is something that I particularly dislike, because it is (for me) very easy to just not see it in a Boolean expression. My objection is the reason that Grace's standard dialect has a not method as well as a ! method, and I have always used not. I was surprised to see kjx write:

In Dafny I tend to define not(e) as a synonym for !e because often I've overlooked the "!"

in other words, to express the same misgiving that I had long felt for !. A more visible alternative, if we want an operator, is ~.

Perhaps we should use the method .not for both Boolean negation and for pattern negation, and the operators & and | for Boolean conjunction and disjunction as well as for pattern conjunction and disjunction?

I think that it's OK to keep the status quo in which types are also patterns, and thus to continue to use & and | for both, since the meanings are identical — I think.

kjx commented 1 year ago

right - but the fundamental question is to which extent do we want to remain compatible with the C/C++/Java/JS/TS/etc operator set?

apblack commented 1 year ago

I don't think that's a "fundamental" question. We agreed from the beginning that Grace would follow other languages where they had blazed a trail that we wanted to follow. We never said that we would follow one language or other blindly.

There are plenty of things in C that we have not adopted, such as casts, and defining && and & on numbers, not to mention the ? : ternary operator for conditional expressions. There are plenty of popular concepts in JS and Python, too, such as truthyness–falsyness, that we rejected because we felt that they were bad ideas. At least two of us agree that ! for boolean negation is a bad idea, and yet we continue to allow it. We also support the .not request on Booleans — at least in part because I wanted .not, to avoid having to use !. The result is that we have two ways of saying the same thing, which we (following the advice of Michael Kölling) agreed was something to be avoided.

I was going to tell you (proudly) that nowhere in minigrace do I use !, always preferring .not. But it turns out that this isn't true. There are a few places where a (necessarily) postfix method request seemed unnatural, compared to a (prefix) operator symbol. For example, in the code generator, there is a line that reads:

if (! part.name.startsWith "$") then {

This is because

if (part.name.startsWith "$".not) then {

implies (falsely) that .not is requested on the string "$", while in

if (part.name.startsWith "$" .not) then {

the .not comes too late: I have to backtrack and reverse the condition after I have visualized it.

if (part.name.doesn'tStartWith "$") then {

would be optimal, but we can't define two variants of every method that answers a boolean.

So, I think that

  1. we should have an operator for boolean negation, as well as, or instead of, the .not named method, and
  2. we should not use ! for boolean negation.

That said, we should probably move all of this discussion of ! to another issue. I think that for the issue at hand — what operator should be use for negating a pattern — we are tending to agree that we should use whatever operator we use for boolean negation. To avoid ambiguity, true and false should not be patterns.