Racket - Closure / Currying, where is the difference?
Asked Answered
H

1

5

So from my personal research, it seems that closures / currying seem to be more or less the exact same thing, which can't be obviously correct. So where is the difference?

So here is an example of a closure in Racket:

(define (make-an-adder x)
  (lambda (y)
    (+ y x)))

(define add3 (make-an-adder 3))


(add3 5)

will give back

8

So where is the difference to currying? Because if i look up documentations and other examples, it seems that they do the exact same thing as i showed for the closure?

Thanks in advance everyone!

Honkytonk answered 21/6, 2020 at 14:16 Comment(2)
I'd say closures are a scope related technique that allows currying in the first place. Currying embodies the mathemetical idea that each lambda has exactly one argument. The only way to express multi-argument functions in such a setting is to nest single-argument ones.Vivacity
currying is a concept / idea, closures are implementation technique / detail.Expectancy
N
13

So they are different concepts, but both are related to nested lambdas.

A Closure can be created by a lambda that refers to a variable defined outside itself, and is most important when the lambda escapes from the context where that outside variable is defined. The Closure's job is to make sure that variable is preserved when the lambda escapes that context.

A Curried function is a function that can take its arguments in multiple steps, or multiple different function-applications. This normally means there are lambdas nested within lambdas.

Curried functions aren't always closures, though they often are

Most useful curried functions need to use closures, but if the inner lambdas ignore the outer arguments, they aren't closures. A simple example:

(define (curried-ignore-first ignored)
  (lambda (y) y))

This is not a closure because the inner lambda (lambda (y) y) is already closed: it doesn't refer to any variables outside itself.

A curried function doesn't always need to ignore the outer arguments... it just needs to be done processing them before it returns the inner lambda, so that the inner lambda doesn't refer to the outer argument. A simple example of this is a curried choose function. The "normal" definition of choose does indeed use a closure:

(define (choose b)
  (lambda (x y)
    (if b x y))) ; inner lambda refers to `b`, so it needs a closure

However, if the if b is put outside the outer lambda, we can avoid making closures:

(define (choose b)
  (if b
      (lambda (x y) x)   ; not closures, just nested lambdas
      (lambda (x y) y)))

Closures aren't always from curried functions

A closure is needed when a inner lambda refers to a variable in an outer context and might escape that context. That outer context is often a function or a lambda, but it doesn't have to be. It can be a let:

(define closure-with-let
  (let ([outer "outer"])
    (lambda (ignored) outer))) ; closure because it refers to `outer`

This is a closure, but not an example of currying.

Turning a Curried-function-producing-a-closure into one without a closure

The example in the original question is a curried function that produces a closure

(define (make-an-adder x)
  (lambda (y)
    (+ y x)))

If you wanted to make a version that's still a curried function with the same behavior, but without needing a closure over x in some special cases, you can branch on those before the lambda:

(define (make-an-adder x)
  (match x
    [0 identity]
    [1 add1]
    [-1 sub1]
    [2 (lambda (y) (+ y 2))]
    [3 (lambda (y) (+ y 3))]
    [_ (lambda (y) (+ y x))]))

This avoids producing a closure for the cases of x being an exact integer -1 through 3, but still produces a closure in all other cases of x. If you restricted the domain of x to a finite set, you could turn it into a function that didn't need closures, just by enumerating all the cases.

If you don't want a closure over x, but you're fine with a closure over other things, you can use recursion and composition to construct an output function that doesn't close over x:

(define (make-an-adder x)
  (cond [(zero? x) identity]
        [(positive-integer? x)
         (compose add1 (make-an-adder (sub1 x)))]
        [(negative-integer? x)
         (compose sub1 (make-an-adder (add1 x)))]))

Note that this still produces closures (since compose creates closures over its arguments), but the function it produces does not close over x. Once this version of make-an-adder produces its result, it's "done" processing x and doesn't need to close over it anymore.

Noonberg answered 21/6, 2020 at 15:10 Comment(4)
Your answer is awesome! One question: Would it be possible to update the answer and give an example how the example that i've posted would look like as currying? Just for good measurement. Only if it doesn't bother you. Thanks already for the answer by thew ay! Helped a lot already!Honkytonk
The example in your question is already both an example of currying and an example of a closure. The currying comes from being able to apply it in multiple steps, and the closure comes from the inner lambda referring to the x from the outer function.Noonberg
Okay, i definitely need to study your answer further in depth. Would it be possible to transform my given example to a currying / closure only example? You gave already great examples, but i wonder how this would look like with my given example.Honkytonk
I'm not sure what you mean by that. I guess you could always turn a currying+closure example into a closure-only example by turning all the outer lambdas into lets, but that makes it much less useful. A much more interesting question is whether you can turn a currying+closure example into an equivalent curried function that doesn't need a closure... my guess is no, not for all examples, but some, like if the outer arguments are all restricted to finite sets, maybe? I don't know thoNoonberg

© 2022 - 2024 — McMap. All rights reserved.