gracelang / language

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

match and successfulMatch(result) #157

Closed apblack closed 6 years ago

apblack commented 6 years ago

I'm trying to get programs through the name resolution phase of SmallGrace without cheating. The thing that has me stuck right now is successfulMatch and the match protocol.

As it is currently specified, if one tries to match a block against an object, the result will either be false, if the object modes not satisfy the patterns specified for the block's parameter, or successfulMatch with a result that is the value returned by the block. successfulMatch is specified as behaving like true.

In most implementations, true and false will be "special"; the compiler will know about their representation and compile if(_)then(_)elseif(_)... inline. This doesn't have to be the case, but I think that it will be common. So if successfulMatch is going to behave like true, it will probably have to be implemented like true as well. Which means ... I'm not sure what, exactly. Probably that successfulMatch will have to be primitive. I'm trying to limit the amount f stuff that is primitive.

An alternative is to make blocks respond to the message matches(subject) rather than match(subject). matches returns true or false when the block's pattern does or does not match the subject. apply raises an exception exactly when matches returns false, and otherwise returns the result of applying the block.

This is potentially less efficient, since apply(subject) will also have to test the pattern. But it's a whole lot simpler.

kjx commented 6 years ago

So if successfulMatch is going to behave like true, it will probably have to be implemented like true as well. Which means ... I'm not sure what, exactly. Probably that successfulMatch will have to be primitive. I'm trying to limit the amount f stuff that is primitive.

Only if you want it to be as efficient (or otherwise) as true. As long as we don't do the Smalltalk thing of hardcoding things like if()then()else (or ifTrue()ifFalse() in the compiler so that they only work with builtin Booleans I can't see there being a problem: most of the Booleans will be 'real' Booleans and thus optimised, while SuccessfulMatch will fall back to a "slow path".

I don't think the spec - or any clean implementation - should do anything special for SuccessfulMatch, or even for Booleans.

(not sure what this has to do about a) name resolution, or b) matches(_) vs match(_)).

KimBruce commented 6 years ago

It's always seemed wrong to me for these things to be subobjects (subclasses) of boolean as it has led to lots of weird statements like if (val != false). I'd rather ask the result whether it succeeded or not (or program default behavior that doesn't require it to masquerade as a boolean).

apblack commented 6 years ago

@kjx: I don't see how to optimize 'real' Booleans if there are also fake Booleans — at least, not without checking every time that the suspected Boolean really is 'real', and branching if it isn't, or taking and handling an exception.

The other problem with failedMatch and successfulMatch (as Michale originally formulated them) is that they require constructing a new object every time a match is attempted. The current formulation is better, since it requires constructing a new object only when a match succeeds (most matches will fail) — but it has the disadvantage, as @KimBruce points out, of requiring that there be more than two booleans.

The proposal that I make in the issue is that we replace match by matches, which actually returns a real Boolean— neither of you have commented on that.

@kjx's response also raises another language specification issue: what exactly is the requirement that if(_)then(_)else(_) imposes on it's first argument? In minigrace, the requirement is that the underlying JavaScrip object can be used as the argument to the JavaScript function Grace_isTrue: not something that we should put in the spec! Is the requirement that the argument respond to ifTrue()ifFalse() ? In other words: if someone smarter than me can figure out a fast-path for intrinsic Booleans, what should the slow path fall back to?

kjx commented 6 years ago

It's always seemed wrong to me for these things to be subobjects (subclasses) of boolean

well they conform to Boolean - they don't have to inherit from Boolean (and quite probably shouldn't)

as it has led to lots of weird statements like if (val != false).

I can't see why you'd need to do that at all; the whole point of the design is that you don't.

I'd rather ask the result whether it succeeded or not

well that would be the classical / functional design...

kjx commented 6 years ago

@kjx: I don't see how to optimize 'real' Booleans if there are also fake Booleans — at least, not without checking every time that the suspected Boolean really is 'real', and branching if it isn't, or taking and handling an exception.

but don't you have to do that anyway? Even in Smalltalk you can write 1 ifTrue: [ "hello" ] and you'll catch that error. You can avoid that check if you know for sure your receiver is a Boolean. All this says is if that check fails then you revert back to mainline code - just the way e.g. Smalltalk handles a SmallIntenger being added to an arbitrary precision real

The proposal that I make in the issue is that we replace match by matches, which actually returns a real Boolean— neither of you have commented on that.

if you want to do that, why not just dump the whole "MatchResult" business, and make match return a Boolean and be done. I can't see why we'd want both.

Although I'm keen on cleaning stuff up and getting rid of some things, these kind of questions make me wonder if we should "kill our darlings" - find everything that is clever or cute or subtle or that we really really like in the language - or ever that needs more than one sentence to describe - and remove it. Under that rubric, matching would either switch to a straight Boolean, or go; the current inheritance design would go; the whole implicit resolution (lexical vs inheritance) would go (the last bit is what I'd least like to lose).

if someone smarter than me can figure out a fast-path for intrinsic Booleans, what should the slow path fall back to?

dunno about the fast path (but see above) - but I can't see why it can't just fall back to Grace code.

In minigrace, the requirement is that the underlying JavaScript object can be used as the argument to the JavaScript function Grace_isTrue:

if we want to, we can require that the receivers are either the "predefined constants" or "literals" true and false --- if the current spec means anything, that's probably it. If we want to simplify, we can just go straight there.

apblack commented 6 years ago

if you want to do that, why not just dump the whole "MatchResult" business, and make match return a Boolean and be done. I can't see why we'd want both.

That's what I'm proposing.

dunno about the fast path (but see above) - but I can't see why it can't just fall back to Grace code.

what I'm asking is: what Grace code. I think that ifTrue(_)ifFalse(_) is a reasonable answer, but we don't say that in the Spec. Indeed, as the Spec is written, it's impossible to implement if()then()else(_).

these kind of questions make me wonder if we should "kill our darlings"

As you know, matching has never been one of my darlings. It's there because the rest of the team wanted it. Which is exactly why I'm being circumspect in proposing modifications: I don't want to accidentally break whatever purpose you feel that it fulfills.

apblack commented 6 years ago

Another possible replacement for aBlock.match(_) is

   aBlock.tryToApplyTo (arg) else (alternativeBlock)

which would return the result of applying aBlock to arg, or of applying alternativeBlock to arg. Nesting this would let us implement match(_)case(_)... in a similar way to the way that we implement if(_)then(_)elseif(_)...else(_) using if(_)then(_)else(_)

kjx commented 6 years ago

I agree andrew will resolve this design

apblack commented 6 years ago

I typed in a long response summarizing the design, but somehow lost it when I opened another tab to look for a URL. I don't have the energy to do it over. In short, I plan to

  1. get rid of match, successfulMatch, and failedMatch
  2. replace them with matches(_), which returns a simple Boolean
  3. implement match(subj)case(casePattern_1)...case(casePattern_n) by making requests casePattern_i.matches(subj) in order until one says true, and then applying casePattern_i. This could be optimized for the common case where the casePatterns are literal blocks.

I know that in the past, @KimBruce has expressed a preference for methods that take two executable blocks over those that answer Booleans, for example in the discussions on the option module. I don't object to this kind of interface (in fact, I suggested it above), but for now the design sketched in this response seems simpler. We could augment matches(_) and apply(_) with a tryToApplyTo (arg) else (alternativeBlock) later.

apblack commented 6 years ago

Incidentally, the matches(_) and apply(_) design generalizes to pattern blocks with 2, 3 or more arguments.