2

suppose I have the following functions:

(define (g x) (f x))
(define (f x) (+ 1 x))

I would like to temporarily call g with a different f. For example, something like this:

(let ((f (lambda (x) (+ 2 x))))
  (g 5))

I would like the code above to evaluate to 7, but it doesn't. Instead, it evaluates to 6, since g calls the f outside the scope of the let.

Is there a way to do this without redefining g inside the let, and without inlining the entire body of the definition of g in the let? (In practice, g may be a very large, complicated function).

Will Ness
  • 70,110
  • 9
  • 98
  • 181
xdavidliu
  • 2,411
  • 14
  • 33
  • 1
    You could add one more parameter to `g`: `(define (g f x) (f x))`. Then `(g f 5)` will be either 6 or 7 depending on which `f` gets passed. – assefamaru Feb 22 '18 at 05:24
  • You're asking for *dynamic scope*, which was discovered to be a not so very good idea in the 1960s. Lexical scope has ruled the world ever since. – molbdnilo Feb 22 '18 at 12:08

4 Answers4

2

What you are asking for is dynamic rather than lexical binding of 'f'. R6RS and R7RS support this with parameters. This will do what you want:

(define f (make-parameter (lambda (x) (+ 1 x))))
(define (g x) ((f) x))

(display (g 5))(newline)

(parameterize ((f (lambda (x) (+ 2 x))))
  (display (g 5))(newline))
Chris Vine
  • 677
  • 3
  • 7
0

I'm not sure that you can, but I'm by no means a Scheme expert.

I realise that you're trying to achieve this without redefining g inside the let, but how about:

(define (h f x) (f x))
(define (g x) (h f x))
(define (f x) (+ 1 x))

(let ((f (lambda (x) (+ 2 x))))
  (h f 5))

That way, you preserve the behaviour of g where it's currently being called. But where you want to temporarily have a different behaviour, you can call h instead.

A bit more code for clarification:

(let ((f (lambda (x) (+ 2 x))))
  (display (g 5)) ; 6
  (newline)
  (h f 5))        ; 7
  • Doesn't this still require me to paste the entire body of the definition of `g` into the `let`? The case that I actually care about is when `g` is a very long, complex function that may call `f` many times. Can this answer handle that case? – xdavidliu Feb 22 '18 at 04:33
  • No, I don't think so. I am assuming that `f` and `g` are globally defined? What I was suggesting was to create a new procedure `h`, and cut/paste the body of `g` into `h`, where `h` takes a procedure `f` as an argument. Then, redefine `g` so that it calls `h` and passes in the globally defined `f`. The behaviour of `g` therefore remains the same, but wherever you'd like to call `g` with a different `f`, you can call `h` and pass in that different `f` instead. –  Feb 22 '18 at 04:47
0

You could use an optional parameter in g to pass the f from the let expression.

(define (g x . args)
  (if (null? args)
    (f x)
    ((car args) x)))

and

(let ((f (lambda (x) (+ 2 x))))
  (g 5 f))
R Sahu
  • 204,454
  • 14
  • 159
  • 270
0

I found a way to do exactly what I wanted, although I have a feeling many people will not consider this kosher:

(define (g x) (f x))

(define (f x) (+ 1 x))

(let ((old-f f))
  (set! f (lambda (x) (+ 2 x)))
  (let ((ans (g 5)))
    (set! f old-f)
    ans))
; -> 7

(g 5) ; -> 6

edit In response to the comment below, I wasn't even aware that fluid-let was a thing. It even already works on MIT-Scheme. That's actually exactly what I needed. If commenter below posts something like this as an answer, it will be made the accepted answer:

(define (g x) (f x))

(define (f x) (+ 1 x))

(fluid-let ((f (lambda (x) (+ x 2))))
  (g 5)) ; -> 7

(g 5) ; -> 6
xdavidliu
  • 2,411
  • 14
  • 33
  • exactly. and this can be packaged into a macro called, I dunno, like, [`fluid-let`](https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Dynamic-Binding.html) or something. Of course we need to add protection against errors, so the old definition of `f` will get restored no matter what, like [`unwind-protect`](http://clhs.lisp.se/Body/s_unwind.htm) does in CL. and what about continuations/long jumps? but as a simple user-level utility, why not, indeed. – Will Ness Mar 12 '18 at 11:05
  • `fluid-let` is exactly what I wanted; feel free to post an answer and I will hit accept – xdavidliu Mar 15 '18 at 19:29
  • Of course you can do it with set! and global variables by hand, but then you have to implement the code to prevent exceptions or the invocation of continuation objects leaving your global variables in an inconsistent state, and this is also not thread safe (parameterize on the other hand is thread local on common implementations, as are some implementations of fluids). You include 'racket' within your groups, and racket has parameterize but not (I believe) fluid-let. More generally, parameterize is in the scheme reports and fluid-let is not. – Chris Vine Mar 17 '18 at 10:53
  • @WillNess yes you are right, I did in fact put Racket as a tag at first. No idea why I did that, since I've almost never used it. – xdavidliu Mar 17 '18 at 20:00