Skip to content

Commit 4d3ff74

Browse files
Copilotjackfirth
andauthored
Add fuse-map-with-for refactoring rule (#778)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jackfirth <8175575+jackfirth@users.noreply.github.com>
1 parent 9dbfa96 commit 4d3ff74

3 files changed

Lines changed: 221 additions & 0 deletions

File tree

default-recommendations.rkt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
resyntax/default-recommendations/let-replacement/match-let-replacement
3232
resyntax/default-recommendations/list-shortcuts
3333
resyntax/default-recommendations/loops/for-loop-shortcuts
34+
resyntax/default-recommendations/loops/fuse-map-with-for
3435
resyntax/default-recommendations/loops/list-loopification
3536
resyntax/default-recommendations/loops/named-let-loopification
3637
resyntax/default-recommendations/match-shortcuts
@@ -73,6 +74,7 @@
7374
resyntax/default-recommendations/let-replacement/match-let-replacement
7475
resyntax/default-recommendations/list-shortcuts
7576
resyntax/default-recommendations/loops/for-loop-shortcuts
77+
resyntax/default-recommendations/loops/fuse-map-with-for
7678
resyntax/default-recommendations/loops/list-loopification
7779
resyntax/default-recommendations/loops/named-let-loopification
7880
resyntax/default-recommendations/match-shortcuts
@@ -104,6 +106,7 @@
104106
exception-suggestions
105107
file-io-suggestions
106108
for-loop-shortcuts
109+
fuse-map-with-for
107110
function-definition-shortcuts
108111
function-shortcuts
109112
hash-shortcuts
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#lang resyntax/test
2+
3+
4+
require: resyntax/default-recommendations/loops/fuse-map-with-for fuse-map-with-for
5+
6+
7+
header:
8+
- #lang racket/base
9+
10+
11+
test: "map producing list for for* loop can be fused"
12+
--------------------
13+
(define (f xs g h)
14+
(define ys (map (λ (x) (g x)) xs))
15+
(for* ([y (in-list ys)]
16+
[z (in-list (h y))])
17+
(displayln z)))
18+
====================
19+
(define (f xs g h)
20+
(for* ([x (in-list xs)]
21+
[y (in-list (g x))]
22+
[z (in-list (h y))])
23+
(displayln z)))
24+
--------------------
25+
26+
27+
test: "map producing list for single-clause for* loop can be fused"
28+
--------------------
29+
(define (f xs g)
30+
(define ys (map (λ (x) (g x)) xs))
31+
(for* ([y (in-list ys)])
32+
(displayln y)))
33+
====================
34+
(define (f xs g)
35+
(for* ([x (in-list xs)]
36+
[y (in-list (g x))])
37+
(displayln y)))
38+
--------------------
39+
40+
41+
test: "map producing list for for loop can be fused"
42+
--------------------
43+
(define (f xs g)
44+
(define ys (map (λ (x) (g x)) xs))
45+
(for ([y (in-list ys)])
46+
(displayln y)))
47+
====================
48+
(define (f xs g)
49+
(for ([x (in-list xs)])
50+
(define y (g x))
51+
(displayln y)))
52+
--------------------
53+
54+
55+
no-change-test: "multi-clause for loop cannot be fused"
56+
--------------------
57+
(define (f xs zs)
58+
(define ys (map (λ (x) (+ x 1)) xs))
59+
(for ([y (in-list ys)]
60+
[z (in-list zs)])
61+
(displayln (list y z))))
62+
--------------------
63+
64+
65+
no-change-test: "map with short lambda but ys used elsewhere not refactorable"
66+
--------------------
67+
(define (f xs g h)
68+
(define ys (map (λ (x) (g x)) xs))
69+
(for* ([y (in-list ys)]
70+
[z (in-list (h y))])
71+
(displayln z))
72+
(displayln ys))
73+
--------------------
74+
75+
76+
test: "map with lambda that has multiple body forms is refactorable"
77+
--------------------
78+
(define (f xs g)
79+
(define ys (map (λ (x) (displayln x) (g x)) xs))
80+
(for ([y (in-list ys)])
81+
(displayln y)))
82+
====================
83+
(define (f xs g)
84+
(for ([x (in-list xs)])
85+
(displayln x)
86+
(define y (g x))
87+
(displayln y)))
88+
--------------------
89+
90+
91+
test: "map with long single-body lambda is refactorable"
92+
--------------------
93+
(define (f xs)
94+
(define long-name 42)
95+
(define ys
96+
(map (λ (x)
97+
(+ x long-name))
98+
xs))
99+
(for ([y (in-list ys)])
100+
(displayln y)))
101+
====================
102+
(define (f xs)
103+
(define long-name 42)
104+
(for ([x (in-list xs)])
105+
(define y (+ x long-name))
106+
(displayln y)))
107+
--------------------
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#lang racket/base
2+
3+
4+
(require racket/contract/base)
5+
6+
7+
(provide
8+
(contract-out
9+
[fuse-map-with-for refactoring-suite?]))
10+
11+
12+
(require resyntax/base
13+
racket/list
14+
resyntax/default-recommendations/analyzers/identifier-usage
15+
resyntax/default-recommendations/let-replacement/private/let-binding
16+
resyntax/default-recommendations/private/lambda-by-any-name
17+
syntax/parse)
18+
19+
20+
;@----------------------------------------------------------------------------------------------------
21+
22+
23+
;; A short lambda suitable for fusing with a for loop. For multi-body lambdas, we need to
24+
;; separate the prefix forms (all but last) from the result form (the last).
25+
(define-syntax-class fuseable-map-lambda
26+
#:attributes (x single-body [multi-body 1] [prefix-forms 1] result-form)
27+
28+
;; Lambdas with let expressions that can be refactored
29+
(pattern
30+
(_:lambda-by-any-name (x:id)
31+
original-body:body-with-refactorable-let-expression)
32+
#:with (multi-body ...) #'(original-body.refactored ...)
33+
#:do [(define refactored-forms (attribute original-body.refactored))
34+
(define prefix-list (if (null? refactored-forms) '() (drop-right refactored-forms 1)))
35+
(define result (if (null? refactored-forms) #'(begin) (last refactored-forms)))]
36+
#:attr [prefix-forms 1] prefix-list
37+
#:attr result-form result
38+
#:attr single-body #'(begin original-body.refactored ...))
39+
40+
;; Lambdas with multiple body forms (two or more)
41+
(pattern (_:lambda-by-any-name (x:id) prefix-form ... last-form)
42+
#:when (not (null? (attribute prefix-form)))
43+
#:with (multi-body ...) #'(prefix-form ... last-form)
44+
#:attr [prefix-forms 1] (attribute prefix-form)
45+
#:attr result-form #'last-form
46+
#:attr single-body #'(begin prefix-form ... last-form))
47+
48+
;; Short lambdas with a single body form
49+
(pattern (_:lambda-by-any-name (x:id) only-form)
50+
#:with (multi-body ...) #'(only-form)
51+
#:attr [prefix-forms 1] '()
52+
#:attr result-form #'only-form
53+
#:attr single-body #'only-form))
54+
55+
56+
(define-definition-context-refactoring-rule fuse-map-with-for*-rule
57+
#:description
58+
"A `map` expression producing a list for a `for*` loop can be fused with the loop."
59+
#:analyzers (list identifier-usage-analyzer)
60+
#:literals (define map in-list for*)
61+
(~seq body-before ...
62+
(define ys:id (map function:fuseable-map-lambda list-expr:expr))
63+
(for*-id:for*
64+
(~and original-clauses
65+
([y-var:id (in-list ys-usage:id)] remaining-clause ...))
66+
for-body ...)
67+
body-after ...)
68+
69+
;; Check that ys is only used in the for loop, not elsewhere
70+
#:when (free-identifier=? (attribute ys) (attribute ys-usage))
71+
#:when (equal? (syntax-property #'ys 'usage-count) 1)
72+
73+
;; Generate the refactored code - fuse as nested clauses
74+
(body-before ...
75+
(for*-id ([function.x (in-list list-expr)]
76+
[y-var (in-list function.single-body)]
77+
remaining-clause ...)
78+
for-body ...)
79+
body-after ...))
80+
81+
82+
;; Rule for when there are single-clause for loops (not for*) - use internal definition
83+
(define-definition-context-refactoring-rule fuse-map-with-for-single-clause-rule
84+
#:description
85+
"A `map` expression producing a list for a single-clause `for` loop can be fused with the loop."
86+
#:analyzers (list identifier-usage-analyzer)
87+
#:literals (define map in-list for)
88+
(~seq body-before ...
89+
(define ys:id (map function:fuseable-map-lambda list-expr:expr))
90+
(for-id:for
91+
(~and original-clauses
92+
([y-var:id (in-list ys-usage:id)]))
93+
for-body ...)
94+
body-after ...)
95+
96+
;; Check that ys is only used in the for loop, not elsewhere
97+
#:when (free-identifier=? (attribute ys) (attribute ys-usage))
98+
#:when (equal? (syntax-property #'ys 'usage-count) 1)
99+
100+
;; Generate the refactored code - use internal definition
101+
(body-before ...
102+
(for-id ([function.x (in-list list-expr)])
103+
function.prefix-forms ...
104+
(define y-var function.result-form)
105+
for-body ...)
106+
body-after ...))
107+
108+
109+
(define-refactoring-suite fuse-map-with-for
110+
#:rules (fuse-map-with-for*-rule
111+
fuse-map-with-for-single-clause-rule))

0 commit comments

Comments
 (0)