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
let
can be expanded to only uselambda
in 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
let
andletrec
but 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 racket
A 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
expand
and 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.