Hello! I am using Racket in a course at my university and I've found it quite enjoyable.
I am working on an assignment where the use of let and let* is limited so as to encourage good functional style. While I understand the motivation, this sent me down a rabbit hole of trying to find a way to write my code using let and let* and writing a program to automatically expand it to use lambda instead.
I posted my question on StackOverflow (linked below), but I'll include the contents here as well.
Any advice would be much appreciated (:
Context
This question is tangentially related to a homework assignment, but I am not looking to have someone do my work for me.
I have an assignment where we are discouraged from overuse of
let,let*, and similar functions to encourage us to "think functionally".I also understand that expressions using
letcan be expanded to only uselambdain the following manner.#lang racket ; using let (let ([x 1] [y 2]) (+ x y)) ; with lambda ((lambda (x y) (+ x y)) 1 2)Looking at this caused me to ask the following question.
The Question
Is there some way to accomplish this expansion programmatically? The only part that should be modified are those using
let,let*, and so on.> (expand-let '(let ([x 1] [y 2]) (+ x y))) '((lambda (x y) (+ x y)) 1 2)It would be cool if this could apply to all "local binding" Racket functions:
let,let*,letrec,let-values,let*-values,letrec-values, and so on.As pointed out in the comments, this doesn't really serve any practical purpose for my assignment, but I am still looking into it as a personal project as I am new to Racket. Any advice is appreciated.
What I've tried
- Sylwester's answer to Not able to use let in Racket discusses the expansion of
letandletrecbut does so by hand.- Alex Knauth's answer to When to use define and when to use let in racket includes a helpful discussion of the scopes of
let,let*, andletrec.- Partial expansion of code in scheme is honestly pretty close to what I am looking for, but I will point out a few differences
I don't need to evaluate the code, simply expand what I consider to be "syntactic sugar" in this context
The question requires
#lang scheme, but this question uses#lang racketA comment refers the OP to Racket's Sandboxed Evaluation but I am not sure that is what I am looking for (advice is appreciated). It seems like this is used to provide some control over how code is evaluated, but what I am looking for is not control over evaluation. It is about expanding functions used for local bindings when they can be rewritten using
lambda.The answer links to
expandand this seems to get me closer (maybe?) but I am struggling to find a way forward.> (syntax-e (expand '(let ([x 1] [y 2]) (+ x y)))) '(#<syntax:/Applications/Racket v8.11.1/collects/racket/private/qq-and-or.rkt:193:51 let-values> #<syntax (((x) (quote 1)) ((y) (quote 2)))> #<syntax:/Applications/Racket v8.11.1/collects/racket/private/kw.rkt:1263:25 (#%app + x y)>)Coding it myself (not a complete solution)
I attempted my own solution as a beginner and got stuck. Any advice is appreciated.
(define (expand-let atomized-expr) (expand-let-helper (syntax->datum (expand atomized-expr)) (lambda (result) result))) (define (expand-let-helper parse-tree return) (define (expand-let-bindings bindings) (map (lambda (binding) (list (caar binding) (expand-let (cadr binding)))) bindings)) (cond [(null? parse-tree) (return '())] [(not (pair? parse-tree)) (return parse-tree)] [(eq? (car parse-tree) '#%app) (expand-let-helper (cdr parse-tree) (lambda (result) (return result)))] [(eq? (car parse-tree) '#%top) (return (cdr parse-tree))] [(eq? (car parse-tree) 'let-values) (let* ([bindings (cadr parse-tree)] [body (cddr parse-tree)] [expanded-bindings (expand-let-bindings bindings)] [vars (map car expanded-bindings)] [vals (map cadr expanded-bindings)]) (expand-let-helper body (lambda (result) (return `((lambda (,@vars) ,@result) ,@vals)))))] [else (expand-let-helper (cdr parse-tree) (lambda (cdr-result) (expand-let-helper (car parse-tree) (lambda (car-result) (return (cons car-result cdr-result))))))]))See results here:
; successful case > ((lambda (x y) (let ((a (+ x y)) (b (- x y))) (* a b))) 1 2) -3 > (expand-let '((lambda (x y) (let ((a (+ x y)) (b (- x y))) (* a b))) 1 2)) '((lambda (x y) ((lambda (a b) (* a b)) (+ x y) (- x y))) '1 '2) > (eval (expand-let '((lambda (x y) (let ((a (+ x y)) (b (- x y))) (* a b))) 1 2))) -3 ; failing cases (pointed out in comments) > (expand-let '(let ([n (random -10 10)]) (cond [(positive? n) 1] [(negative? n) -1] [else 0]))) '((lambda (n) (if (positive? n) '1 (if (negative? n) '-1 '0))) (random '-10 '10))
Edit #1: Edited to add more links; I was originally restricted as a new user.
Edit #2: Updated the quoted text from the StackOverflow question.