-
Notifications
You must be signed in to change notification settings - Fork 5
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
Pattern Matching #17
base: master
Are you sure you want to change the base?
Pattern Matching #17
Conversation
b515769
to
ffa53ce
Compare
commit a6224c4 Author: William C. Johnson <[email protected]> Date: Wed May 17 01:00:30 2017 -0400 Revert "1.0.3" This reverts commit 084045a. commit 084045a Author: William C. Johnson <[email protected]> Date: Wed May 17 00:58:45 2017 -0400 1.0.3 commit f2572dd Author: William C. Johnson <[email protected]> Date: Tue May 16 22:34:24 2017 -0400 Parse consequent bindings commit 1a73a60 Author: William C. Johnson <[email protected]> Date: Tue May 16 21:51:45 2017 -0400 Update fixtures commit 586bf27 Author: William C. Johnson <[email protected]> Date: Tue May 16 21:50:12 2017 -0400 Enable parsing for `MatchStatement`s
(significant pertinent discussion has been occurring on this thread) |
Branching from the above, @wcjohnson, I'm considering moving from
note that
EDIT: I think I'll stick with |
I feel good about where the syntax/parsing is at now. My biggest concern with the syntax is that the indentation is a bit shallow:
it's a little hard to distinguish between tests and consequents, visually. changing to, eg,
(at least the start of the consequent wouldn't line up exactly with the start of the test, which happens now). thoughts? match node:
| ~isa("IfStatement"):
add(path.get("consequent"))
add(path.get("alternate"))
| ~isa("DoExpression"):
add(path.get("body"))
| allowLoops and (~isa("For") or ~isa("While")):
add(path.get("body"))
| ~isa("Program") or ~isa("BlockStatement"):
add(path.get("body").pop())
| ~isa("TryStatement"):
add(path.get("block"))
add(path.get("handler"))
add(path.get("finalizer"))
| else:
paths.push(path) would become: match node:
case ~isa("IfStatement"):
add(path.get("consequent"))
add(path.get("alternate"))
case ~isa("DoExpression"):
add(path.get("body"))
case allowLoops and (~isa("For") or ~isa("While")):
add(path.get("body"))
case ~isa("Program") or ~isa("BlockStatement"):
add(path.get("body").pop())
case ~isa("TryStatement"):
add(path.get("block"))
add(path.get("handler"))
add(path.get("finalizer"))
else:
paths.push(path) |
I agree that the fact that the delimiter is 2 characters long creates an unfortunate situation with indent alignment, but I also like the concision and visual flow of |
Actually, looking at the example above, I like the |
Copping a few more examples: export toStatement(body) ->
t = getTypes()
match body:
| ~isa("Expression"):
buildAtLoc(getLoc(body), t.expressionStatement, body)
| else: body
export toFunctionBody(body) ->
match body:
| ~isa("Expression"): body
| else: toBlockStatement(body) would become export toStatement(body) ->
t = getTypes()
match body:
case ~isa("Expression"):
buildAtLoc(getLoc(body), t.expressionStatement, body)
else: body
export toFunctionBody(body) ->
match body:
case ~isa("Expression"): body
else: toBlockStatement(body) |
Other examples might make export toBlockStatement(body) ->
t = getTypes()
bodyLoc = getLoc(body)
match body:
| ~isa("BlockStatement"): body
| ~isa("Statement"): buildAtLoc(bodyLoc, t.blockStatement, [body])
| else: buildAtLoc(bodyLoc, t.blockStatement, [
buildAtLoc(bodyLoc, t.expressionStatement, body)
]) export blockToExpression(path) ->
t = getTypes()
node = path.node
match node:
| ~isa("BlockStatement"):
path.canSwapBetweenExpressionAndStatement = () => true
// XXX: source maps - this code is inside babel and makes unmapped
// nodes.
path.replaceExpressionWithStatements(path.node.body)
path.node
| ~isa("FunctionDeclaration"):
// Special case: the parser promotes a block consisting of a single declaration
// from ExprStatement(FunctionExpr) to FunctionDeclaration. Convert back to
// expression here.
nextNode = t.clone(node)
if nextNode.type === "NamedArrowDeclaration":
nextNode.type = "NamedArrowExpression";
else:
nextNode.type = "FunctionExpression";
nextNode
| ~isa("ExpressionStatement"):
node.expression
| else:
node |
export toBlockStatement(body) ->
t = getTypes()
bodyLoc = getLoc(body)
match body:
| ~isa("BlockStatement"): body
| ~isa("Statement"): buildAtLoc(bodyLoc, t.blockStatement, [body])
| else: buildAtLoc(bodyLoc, t.blockStatement, [
buildAtLoc(bodyLoc, t.expressionStatement, body)
]) export blockToExpression(path) ->
t = getTypes()
node = path.node
match node:
| ~isa("BlockStatement"):
path.canSwapBetweenExpressionAndStatement = () => true
// XXX: source maps - this code is inside babel and makes unmapped
// nodes.
path.replaceExpressionWithStatements(path.node.body)
path.node
| ~isa("FunctionDeclaration"):
// Special case: the parser promotes a block consisting of a single declaration
// from ExprStatement(FunctionExpr) to FunctionDeclaration. Convert back to
// expression here.
nextNode = t.clone(node)
if nextNode.type === "NamedArrowDeclaration":
nextNode.type = "NamedArrowExpression";
else:
nextNode.type = "FunctionExpression";
nextNode
| ~isa("ExpressionStatement"):
node.expression
| else:
node |
In terms of syntax only I lean toward Here's another thing though, do you think the familiarity of If going the |
Definitely seems that Also definitely think I agree that I am somewhat concerned that |
Another possibility would be to allow either, but I think this feature is dizzying enough as it is. |
In super-simple cases like this: match body:
| ~isa("Expression"): body
| else: toBlockStatement(body) I can see the if body~isa('Expression'): body
else: toBlockStatement(body) I don't think that's necessarily a bad thing; match is super great, but idk if it should really be used for every conditional. |
I would characterize that particular example (or more generally, branching on the type of something) as an ideal case for using |
Probably going to let this simmer for a bit while I implement the compiler bit for the feature. |
@wcjohnson interesting – things like that were why I hesitated to build |
(Jon's a friend I'm meeting up with soon) Just pushed a commit that allows both EDIT: One idea: |
@JonAbrams I think classes & result = match value:
| instanceof Foo: `it's a Foo!`
| /^hello$/: `it's a greeting!`
| /^bye$/: `it's a farewell!` I think the current |
Definitely think we should have just one only but this will be good to mess with. |
Actually, while we're on the subject of
But it may be better to have special-case code that compiles it into That would, of course, add additional complexity to the construct (now devs have to remember that regexps are special cases -- your consequent gets the match object rather than the discriminant) but the full match object would make matching on regexps way more powerful. |
Coming back to this, the aesthetics of My biggest concern with it is multiline conditionals, which would be especially confusing: match x:
| ~someLongNamedFn(otherArg, otherOtherArg) or
~someOtherLongNamedFn(otherArg, otherOtherArg) or
~someOtherOherLongNamedFn(otherArg, otherOtherArg):
doSomeLongNamedFnThing(x, otherArg, otherOtherArg)
| ~someLongNamedFn(otherArg, otherOtherArg)
or ~someOtherLongNamedFn(otherArg, otherOtherArg)
or ~someOtherOherLongNamedFn(otherArg, otherOtherArg):
doSomeLongNamedFnThing(x, otherArg, otherOtherArg)
| ~someLongNamedFn(otherArg, otherOtherArg) or
~someOtherLongNamedFn(otherArg, otherOtherArg) or
~someOtherOherLongNamedFn(otherArg, otherOtherArg):
doSomeLongNamedFnThing(x, otherArg, otherOtherArg)
| (
~someLongNamedFn(otherArg, otherOtherArg) or
~someOtherLongNamedFn(otherArg, otherOtherArg) or
~someOtherOherLongNamedFn(otherArg, otherOtherArg)
):
doSomeLongNamedFnThing(x, otherArg, otherOtherArg) Of those, I like the 2nd and 4th best, personally... but 2nd clashes with the way most people do multiline conditionals now (at least, how So as long as there's a style which assuages this, I think I feel okay about it. Hopefully a linter/formatter enforces one style in the future. |
(apologies for the deluge) ...another option would be to go back to We left that because of the However, by disabling function syntax in match-test's, which wouldn't make any sense anyway, this could be lifted, allowing comma-free syntax. That would still leave the problem of how to rename/destructure params, and I think
This would be moderately messy in the parser, but not the end of the world – two or three places, I think, where Hackier (but less messy) would be the fact that the
This would allow for slightly surprising (but, I think, desired) behavior in
EDIT: When I originally conceived the feature, I had the even more space-age syntax:
which visually I like, but couldn't figure out a way to add renaming/destructuring... (EDIT: trying a |
I definitely agree there's an issue with some of that multiline test expression formatting, and I think having a consistent style guide between
I also don't love making up new kinds of arrows. Already I think things like Out of the available options, I think a recommended style for multiline test expressions is better than the others. If that isn't sufficient to solve the issue to everyone's satisfaction, then I myself prefer going to |
I agree on all @wcjohnson 's points there. Have you come across Brian Terlson's strawman proposal for JS? I'm just wondering, not proposing we use this necessarily - it's very different from this but probably worth a look if you haven't seen it. It's an alternative https://gist.github.com/bterlson/da8f02b95b484cd4f8d9#file-patternmatching-md |
Another possible style: match x:
| ~test1()
or ~test2()
or ~test3():
result |
I hadn't seen that proposal before, thanks for pointing it out. Worth considering for sure. The I think the point about not overloading Sounds like there's no enthusiasm for Will probably stick with The power of destructuring before the test is appealing, though, which might change the grammar... In general, being able to match on multiple values would be great. |
Regarding auto-instanceof, my input would be that I kind of like it (especially the |
Nice, well spelled out! oh, but it is the logical operator 😉 that's the beauty of it. You could also do things like that said, I see your point, and will keep that alternative in mind if people find this confusing. Agreed about the dangers of cognitive overload, though in the |
So if I want to test the discriminant, non-assertively destructure, then test one of the bound vars, what do I write? Would it be
|
no, you wouldn't be able to do that, which I think makes sense, because EDIT: you could, however, do ... the more I think about it, the more I think non-assertion should be opt-in, which is easy with |
Both price and color are safe, because |
hmm, k. I guess I just don't currently see that as such a problem, but open to being proven wrong. If it's a performance concern (seems questionable), can always use |
2815b3d
to
85d3bb1
Compare
plugin PR at lightscript/babel-plugin-lightscript#22 |
Jumping in late to this discussion, but can I throw this in please? How about omitting any symbol (like
Whatever syntax is decided on regarding |
Hi @DuncanMacWeb , thanks for pitching in! Given the direction the TC39 pattern-matching proposal is headed, it's very likely that syntax will be adopted in LightScript as well (eg; your proposal will probably be adopted 😄 ). I'm currently waiting to see how that proposal shakes out before taking further action here. |
@rattrayalex good plan, keeping LightScript as close as possible to JS — keeps the learning curve shallow for developers from a JS background 👍 |
Won't merge until the compiler implementation is done (haven't started it yet).
Example:
This also should be preceded by outlawing of naked decimals (enforcing
0.123
instead of.123
) and removing all bitwise operators from ordinary use.