-
-
Notifications
You must be signed in to change notification settings - Fork 194
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
Refactor ASTTransformer #1497
Conversation
PRUbuntu
Windows
Mac
MasterUbuntu
Windows
Mac
|
I'll try and review this tomorrow (Monday) - although I'm only just back from a period of leave, so I might be swamped. |
949049c
to
5437946
Compare
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)) |
There was a problem hiding this comment.
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
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 :( |
Hey Patrick, good feedback. This is indeed a too large PR that does multiple things. |
yield! nodes ] | ||
|> finalContinuation) | ||
| SynExpr.Quote (operator, _, quotedSynExpr, _, range) -> | ||
let continuations : ((TriviaNodeAssigner list -> TriviaNodeAssigner list) -> TriviaNodeAssigner list) list = |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
src/Fantomas/AstTransformer.fs
Outdated
[ visit operator; visit quotedSynExpr ] | ||
|
||
let finalContinuation (nodes: TriviaNodeAssigner list list) : TriviaNodeAssigner list = | ||
[ mkNode SynExpr_Quote range |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
src/Fantomas/AstTransformer.fs
Outdated
exprs |> List.map visit | ||
|
||
let finalContinuation (nodes: TriviaNodeAssigner list list) : TriviaNodeAssigner list = | ||
[ mkNode SynExpr_Tuple range |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
004e886
to
05c56c7
Compare
There was a problem hiding this 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 = |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
src/Fantomas/AstTransformer.fs
Outdated
[ visit operator; visit quotedSynExpr ] | ||
|
||
let finalContinuation (nodes: TriviaNodeAssigner list list) : TriviaNodeAssigner list = | ||
[ mkNode SynExpr_Quote range |
There was a problem hiding this comment.
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.
src/Fantomas/AstTransformer.fs
Outdated
exprs |> List.map visit | ||
|
||
let finalContinuation (nodes: TriviaNodeAssigner list list) : TriviaNodeAssigner list = | ||
[ mkNode SynExpr_Tuple range |
There was a problem hiding this comment.
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 -> |
There was a problem hiding this comment.
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.
784b0dc
to
53ae465
Compare
Thanks for the review @Smaug123! |
Refactored ASTTransformer:
Please review if I missed something.