3

Currently working through the Racket Guide at https://docs.racket-lang.org and reading up on lambda functions. The explanation of their usefulness is lucid, but I am not sure I quite grasp the order in which such functions are evaluated. Consider the following example from the Guide:

(define (twice f v)
  (f (f v)))

(define (make-add-suffix s2)
  (lambda (s) (string-append s s2)))

(twice (make-add-suffix "!") "hello")

The function call to twice here is said to evaluate to "hello!!". Here is my guess at what the evaluation process looks like:

(twice (make-add-suffix "!") "hello")

((make-add-suffix "!") ((make-add-suffix "!") "hello")

((make-add-suffix "!") (string-append "hello" "!"))

(string-append (string-append "hello" "!") "!")

(string-append "hello!" "!")

"hello!!"

Is this an accurate assessment, or have I missed something?

user242007
  • 286
  • 3
  • 13
  • Make-add-suffix is called only once. The resulting value, which is a function, is called two times. Most programming languages (but there are exceptions), don't texturally rewrite and call multiple times as your expansion does. – Joshua Taylor Feb 18 '20 at 02:16

2 Answers2

2

Slogan: The outermost and left-most nested expression should be evaluated first.

(twice (make-add-suffix "!") "hello")

; (define (f x) ...) is short for (define f (lambda (x) ...)) so,
; = { substitute twice with  (lambda (f v) (f (f v)))}

((lambda (f v) (f (f v))) (make-add-suffix "!") "hello")

; = { substition of make-add-suffix with (lambda (s2) (lambda (s) (string-append s s2)))}

((lambda (f v) (f (f v)))
 ((lambda (s2) (lambda (s) (string-append s s2))) "!")
 "hello")

Terminology before we move on:

  • Beta Reduction: ((lambda (x-1 ... x-n) f-body) v-1 ... v-n) == f-body with all occurrences of x-1 ... x-n replaced with v-1 ... v-n, respectively.

  • Call by Value: The arguments of a function call are evaluated before beta reduction.

; = { beta-reduction of ((lambda (s2) (lambda (s) (string-append s s2))) "!") }

((lambda (f v) (f (f v))) (lambda (s) (string-append s "!")) "hello")

; = { beta-reduction of the whole expression }

((lambda (s) (string-append s "!"))
 ((lambda (s) (string-append s "!")) "hello"))

; = { beta-reduction of the expression in the argument position first }

((lambda (s) (string-append s "!")) (string-append "hello" "!"))

; ... and the rest is easy:
((lambda (s) (string-append s "!")) "hello!")
(string-append "hello!" "!")
"hello!!"
Atharva Shukla
  • 2,098
  • 8
  • 21
1

A different way of getting to the same answer: DrRacket includes a "Stepper" tool for exactly this purpose. If you set the language level to "Intermediate Student with Lambda" and click on the "Step" button, you should be able to see the evaluation of your program as a sequence of steps, as thrvshl describes.

EDIT: the evaluation strategy you describe, where the first argument to twice is substituted for each of the instances of x in the definition of twice, is called "call-by-name" evaluation, and associated with laziness a la Haskell. To see the difference, consider a version of make-add-suffix that includes a printf in the inner lambda

John Clements
  • 16,895
  • 3
  • 37
  • 52