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

Refactor ASTTransformer #1497

Merged
merged 8 commits into from
Mar 13, 2021
Merged

Refactor ASTTransformer #1497

merged 8 commits into from
Mar 13, 2021

Conversation

nojaf
Copy link
Contributor

@nojaf nojaf commented Feb 27, 2021

Refactored ASTTransformer:

Please review if I missed something.

@nojaf
Copy link
Contributor Author

nojaf commented Feb 27, 2021

PR

Ubuntu

Method Mean Error StdDev Rank Gen 0 Gen 1 Gen 2 Allocated
Format 2.513 s 0.0383 s 0.0358 s 1 123000.0000 44000.0000 5000.0000 1.76 GB

Windows

Method Mean Error StdDev Rank Gen 0 Gen 1 Gen 2 Allocated
Format 3.071 s 0.0488 s 0.0433 s 1 102000.0000 32000.0000 3000.0000 1.76 GB

Mac

Method Mean Error StdDev Rank Gen 0 Gen 1 Gen 2 Allocated
Format 2.765 s 0.0412 s 0.0385 s 1 300000.0000 121000.0000 5000.0000 1.76 GB

Master

Ubuntu

Method Mean Error StdDev Rank Gen 0 Gen 1 Gen 2 Allocated
Format 3.228 s 0.0469 s 0.0416 s 1 77000.0000 21000.0000 5000.0000 1.79 GB

Windows

Method Mean Error StdDev Rank Gen 0 Gen 1 Gen 2 Allocated
Format 2.676 s 0.0526 s 0.0541 s 1 77000.0000 21000.0000 5000.0000 1.79 GB

Mac

Method Mean Error StdDev Rank Gen 0 Gen 1 Gen 2 Allocated
Format 3.039 s 0.0599 s 0.0641 s 1 304000.0000 121000.0000 4000.0000 1.79 GB

@nojaf nojaf changed the title Refactor ast viewer Refactor ASTTransformer Feb 27, 2021
@Smaug123
Copy link
Contributor

I'll try and review this tomorrow (Monday) - although I'm only just back from a period of leave, so I might be swamped.

@nojaf nojaf force-pushed the refactor-ast-viewer branch 3 times, most recently from 949049c to 5437946 Compare March 6, 2021 14:02
@Smaug123
Copy link
Contributor

Smaug123 commented Mar 7, 2021

I've sat down to look at this a few times, and… it's just too big for me to meaningfully review. How confident are you in the change, and/or is there any particular bit you'd like me to look at?

let rec sequence<'a, 'ret> (recursions: (('a -> 'ret) -> 'ret) list) (finalContinuation: 'a list -> 'ret) : 'ret =
match recursions with
| [] -> [] |> finalContinuation
| recurse :: recurses -> recurse (fun ret -> sequence recurses (fun rets -> ret :: rets |> finalContinuation))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's actually a slightly different phrasing of this that I prefer, but I'm still fighting to get the blog post through review :P I don't think there's anything wrong with this version, it just has a very odd order of evaluation.

For reference (though I don't think you need to change what you have), my preferred version is:

let rec sequence
    (results : (('result -> 'ret) -> 'ret) list)
    (andThen : 'result list -> 'ret)
    : 'ret
    =
    match results with
    | [] -> andThen []
    | andThenInner :: andThenInners ->
        fun (results : 'result list) ->
            fun (result : 'result) ->
                result :: results
                |> andThen
            |> andThenInner
        |> sequence andThenInner

@Smaug123
Copy link
Contributor

Smaug123 commented Mar 7, 2021

One thing that would help a lot, I think, is if you broke this out into two different reviews: one for "Threw out ASTNodes that we never use" and "Threw out properties and FsAstNode as they are not used.", and one for the CPS transform. I understand how painful this will be to extract, though :(

@nojaf
Copy link
Contributor Author

nojaf commented Mar 8, 2021

Hey Patrick, good feedback. This is indeed a too large PR that does multiple things.
I'd mostly like a review of the CPS stuff. I'll annotate one function and ask some specific questions.
The answers will apply to all of them.
I'm actually very confident about the entire PR.

yield! nodes ]
|> finalContinuation)
| SynExpr.Quote (operator, _, quotedSynExpr, _, range) ->
let continuations : ((TriviaNodeAssigner list -> TriviaNodeAssigner list) -> TriviaNodeAssigner list) list =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it makes sense to use the sequence helper function when the list is fixed and small (2 elements in this case)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's nothing wrong with it, although the situation comes up so infrequently that I would certainly not call myself an expert on style. If you wanted to do it by hand, maybe it would be a bit easier to read - the sequence operator is a bit arcane, and continuation-passing style is already arcane, so maybe being explicit is better than using fancy machinery here. I think it's up to you, really.

[ visit operator; visit quotedSynExpr ]

let finalContinuation (nodes: TriviaNodeAssigner list list) : TriviaNodeAssigner list =
[ mkNode SynExpr_Quote range
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used List.collect a lot of times, any problems or improvements there?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of an alternative.

exprs |> List.map visit

let finalContinuation (nodes: TriviaNodeAssigner list list) : TriviaNodeAssigner list =
[ mkNode SynExpr_Tuple range
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would mkNode SynExpr_Tuple range :: (List.collect id nodes) be better? I seem to switch styles here and there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pff, I really don't mind. Personally I prefer to be explicit if I use the yield keyword - I'm not a fan of implicit yields, because if you omit the yields then you have the chance in principle to get confused between [if true then yield 3 else yield 4] and [yield (if true then 3 else 4)].

I suspect mkNode SynExpr_Tuple range :: (List.collect id nodes) might be more efficient - it would certainly rely less on the compiler's magic ability to optimise. It's not obvious to me that the computation expression avoids copying whatever is yield!'ed.

@nojaf nojaf force-pushed the refactor-ast-viewer branch 2 times, most recently from 004e886 to 05c56c7 Compare March 12, 2021 12:57
Copy link
Contributor

@Smaug123 Smaug123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I certainly haven't read the whole thing, but I think it looks basically good. I have no fundamental comments other than "is there anywhere you could be more generic?".

yield! nodes ]
|> finalContinuation)
| SynExpr.Quote (operator, _, quotedSynExpr, _, range) ->
let continuations : ((TriviaNodeAssigner list -> TriviaNodeAssigner list) -> TriviaNodeAssigner list) list =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's nothing wrong with it, although the situation comes up so infrequently that I would certainly not call myself an expert on style. If you wanted to do it by hand, maybe it would be a bit easier to read - the sequence operator is a bit arcane, and continuation-passing style is already arcane, so maybe being explicit is better than using fancy machinery here. I think it's up to you, really.

and visitSynExpr (synExpr: SynExpr) : TriviaNodeAssigner list =
let rec visit
(synExpr: SynExpr)
(finalContinuation: TriviaNodeAssigner list -> TriviaNodeAssigner list)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I find these things easier to write correctly if I stay generic as long as possible. It makes it harder when you can't put generic type parameters on, like in an inner function, but if finalContinuation returned a 'ret then the types constrain you further and it's harder to get it wrong. I'm not sure whether that's possible here.

[ visit operator; visit quotedSynExpr ]

let finalContinuation (nodes: TriviaNodeAssigner list list) : TriviaNodeAssigner list =
[ mkNode SynExpr_Quote range
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of an alternative.

exprs |> List.map visit

let finalContinuation (nodes: TriviaNodeAssigner list list) : TriviaNodeAssigner list =
[ mkNode SynExpr_Tuple range
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pff, I really don't mind. Personally I prefer to be explicit if I use the yield keyword - I'm not a fan of implicit yields, because if you omit the yields then you have the chance in principle to get confused between [if true then yield 3 else yield 4] and [yield (if true then 3 else 4)].

I suspect mkNode SynExpr_Tuple range :: (List.collect id nodes) might be more efficient - it would certainly rely less on the compiler's magic ability to optimise. It's not obvious to me that the computation expression avoids copying whatever is yield!'ed.

| SynExpr.New (_, typeName, expr, range) ->
visit
expr
(fun nodes ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got a post coming out soon where I talk about continuation-passing style all over again, and in the course of that, I ended up much preferring the following layout just on aesthetic grounds:

(fun nodes ->
    blah
)
|> visit expr

Saves a layer of indentation, although it is quite weird from an imperative-programming point of view.

@nojaf nojaf merged commit 75f0dfc into fsprojects:master Mar 13, 2021
@nojaf nojaf deleted the refactor-ast-viewer branch March 13, 2021 09:04
@nojaf
Copy link
Contributor Author

nojaf commented Mar 13, 2021

Thanks for the review @Smaug123!

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

Successfully merging this pull request may close these issues.

None yet

2 participants