Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions default-recommendations.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
resyntax/default-recommendations/let-replacement/match-let-replacement
resyntax/default-recommendations/list-shortcuts
resyntax/default-recommendations/loops/for-loop-shortcuts
resyntax/default-recommendations/loops/fuse-map-with-for
resyntax/default-recommendations/loops/list-loopification
resyntax/default-recommendations/loops/named-let-loopification
resyntax/default-recommendations/match-shortcuts
Expand Down Expand Up @@ -73,6 +74,7 @@
resyntax/default-recommendations/let-replacement/match-let-replacement
resyntax/default-recommendations/list-shortcuts
resyntax/default-recommendations/loops/for-loop-shortcuts
resyntax/default-recommendations/loops/fuse-map-with-for
resyntax/default-recommendations/loops/list-loopification
resyntax/default-recommendations/loops/named-let-loopification
resyntax/default-recommendations/match-shortcuts
Expand Down Expand Up @@ -104,6 +106,7 @@
exception-suggestions
file-io-suggestions
for-loop-shortcuts
fuse-map-with-for
function-definition-shortcuts
function-shortcuts
hash-shortcuts
Expand Down
83 changes: 83 additions & 0 deletions default-recommendations/loops/fuse-map-with-for-test.rkt
Comment thread
jackfirth marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#lang resyntax/test


require: resyntax/default-recommendations/loops/fuse-map-with-for fuse-map-with-for


header:
- #lang racket/base


test: "map producing list for for* loop can be fused"
--------------------
(define (f xs g h)
(define ys (map (λ (x) (g x)) xs))
(for* ([y (in-list ys)]
[z (in-list (h y))])
(displayln z)))
====================
(define (f xs g h)
(for* ([x (in-list xs)]
[y (in-list (g x))]
[z (in-list (h y))])
(displayln z)))
--------------------


test: "map producing list for for loop can be fused"
--------------------
(define (f xs g)
(define ys (map (λ (x) (g x)) xs))
(for ([y (in-list ys)])
(displayln y)))
====================
(define (f xs g)
(for ([x (in-list xs)])
(define y (g x))
(displayln y)))
--------------------


no-change-test: "map with short lambda but ys used elsewhere not refactorable"
--------------------
(define (f xs g h)
(define ys (map (λ (x) (g x)) xs))
(for* ([y (in-list ys)]
[z (in-list (h y))])
(displayln z))
(displayln ys))
--------------------


test: "map with lambda that has multiple body forms is refactorable"
--------------------
(define (f xs g)
(define ys (map (λ (x) (displayln x) (g x)) xs))
(for ([y (in-list ys)])
(displayln y)))
====================
(define (f xs g)
(for ([x (in-list xs)])
(displayln x)
(define y (g x))
(displayln y)))
--------------------


test: "map with long single-body lambda is refactorable"
--------------------
(define (f xs)
(define long-name 42)
(define ys
(map (λ (x)
(+ x long-name))
xs))
(for ([y (in-list ys)])
(displayln y)))
====================
(define (f xs)
(define long-name 42)
(for ([x (in-list xs)])
(define y (+ x long-name))
(displayln y)))
--------------------
111 changes: 111 additions & 0 deletions default-recommendations/loops/fuse-map-with-for.rkt
Comment thread
jackfirth marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#lang racket/base


(require racket/contract/base)


(provide
(contract-out
[fuse-map-with-for refactoring-suite?]))


(require resyntax/base
racket/list
resyntax/default-recommendations/analyzers/identifier-usage
resyntax/default-recommendations/let-replacement/private/let-binding
resyntax/default-recommendations/private/lambda-by-any-name
syntax/parse)


;@----------------------------------------------------------------------------------------------------


;; A short lambda suitable for fusing with a for loop. For multi-body lambdas, we need to
;; separate the prefix forms (all but last) from the result form (the last).
(define-syntax-class fuseable-map-lambda
#:attributes (x single-body [multi-body 1] [prefix-forms 1] result-form)

;; Lambdas with let expressions that can be refactored
(pattern
(_:lambda-by-any-name (x:id)
original-body:body-with-refactorable-let-expression)
#:with (multi-body ...) #'(original-body.refactored ...)
#:do [(define refactored-forms (attribute original-body.refactored))
(define prefix-list (if (null? refactored-forms) '() (drop-right refactored-forms 1)))
(define result (if (null? refactored-forms) #'(begin) (last refactored-forms)))]
#:attr [prefix-forms 1] prefix-list
#:attr result-form result
#:attr single-body #'(begin original-body.refactored ...))

;; Lambdas with multiple body forms (two or more)
(pattern (_:lambda-by-any-name (x:id) prefix-form ... last-form)
#:when (not (null? (attribute prefix-form)))
#:with (multi-body ...) #'(prefix-form ... last-form)
#:attr [prefix-forms 1] (attribute prefix-form)
#:attr result-form #'last-form
#:attr single-body #'(begin prefix-form ... last-form))

;; Short lambdas with a single body form
(pattern (_:lambda-by-any-name (x:id) only-form)
#:with (multi-body ...) #'(only-form)
#:attr [prefix-forms 1] '()
#:attr result-form #'only-form
#:attr single-body #'only-form))


(define-definition-context-refactoring-rule fuse-map-with-for-rule
#:description
"A `map` expression producing a list for a `for` loop can be fused with the loop."
#:analyzers (list identifier-usage-analyzer)
#:literals (define map in-list for for*)
(~seq body-before ...
(define ys:id (map function:fuseable-map-lambda list-expr:expr))
((~or for-id:for for-id:for*)
(~and original-clauses
([y-var:id (in-list ys-usage:id)] remaining-clause ...+))
for-body ...)
body-after ...)

;; Check that ys is only used in the for loop, not elsewhere
#:when (free-identifier=? (attribute ys) (attribute ys-usage))
#:when (equal? (syntax-property #'ys 'usage-count) 1)

;; Generate the refactored code - fuse as nested clauses
(body-before ...
(for-id ([function.x (in-list list-expr)]
[y-var (in-list function.single-body)]
remaining-clause ...)
for-body ...)
body-after ...))


;; Rule for when there are no remaining clauses - use internal definition
(define-definition-context-refactoring-rule fuse-map-with-for-single-clause-rule
#:description
"A `map` expression producing a list for a `for` loop can be fused with the loop."
#:analyzers (list identifier-usage-analyzer)
#:literals (define map in-list for for*)
(~seq body-before ...
(define ys:id (map function:fuseable-map-lambda list-expr:expr))
((~or for-id:for for-id:for*)
(~and original-clauses
([y-var:id (in-list ys-usage:id)]))
for-body ...)
body-after ...)

;; Check that ys is only used in the for loop, not elsewhere
#:when (free-identifier=? (attribute ys) (attribute ys-usage))
#:when (equal? (syntax-property #'ys 'usage-count) 1)

;; Generate the refactored code - use internal definition
(body-before ...
(for-id ([function.x (in-list list-expr)])
function.prefix-forms ...
(define y-var function.result-form)
for-body ...)
body-after ...))


(define-refactoring-suite fuse-map-with-for
#:rules (fuse-map-with-for-rule
fuse-map-with-for-single-clause-rule))