Reputation: 3867
I'm taking a class that uses ML, and we're going over closures, but I don't quite understand them, especially in ML. I took notes in class and they don't make much sense to me / provide enough detail. I tried looking around online for more info but couldn't find any.
Does anyone know of any resources about closures in ML (or about ML / closures in general) that are pretty good?
Or if someone can post a few general thoughts / explanations on how to implement a closure in ML or how a closure in ML would look like, what a closure is, etc. I'd really appreciate it. I'm just trying to understand the concept / use of closures.
Thanks in advance!
Upvotes: 0
Views: 419
Reputation: 5843
I don't know any ML, so my answer will be in Scheme, which is another functional programming language. I'll assume that you don't know Scheme, and will comment my code well.
**Note: Scheme is dynamically typed, so you won't see any type declarations (like int, float, char, etc.).
(define (double x) ;this defines a new function called "double" which takes one argument called x
(* 2 x)) ;return 2 times its argument. Function application in Scheme takes the form (function arg1 arg2 ...)
We would use this newly defined function like this:
(double 10) ;returns 20
(double 8) ;returns 16
In this function, the variable x
is a local variable. x
is a formal parameter of double. Whenever we use x
in the body of double, there is no doubt as to what value we mean. But what about this:
(define (foo x) ;define a function "foo" which takes on argument called x
(* x a)) ;return x times a
Once again, x
is a formal parameter. But what about a
? What do we mean when we use a
in the body of foo? a
is not defined in foo, so it is called a free variable. To see what a
means, we have to look outside foo. For example, suppose that foo was defined in this context:
(define (bar a) ;define a function "bar" which takes one argument called "a"
(define (foo x) ;define "foo" as an inner function of bar
(* x a))) ;foo returns x * a
Now we know what a
means. Scheme (and also ML) is lexically-scoped, which means that to find out what the variable a
means, we look at the textual context (hopefully this makes sense).
Now, the above definition of bar
is actually incorrect: even thought foo
returns something, bar
doesn't return anything. It defines foo
as an inner function, but then there is no statement following that definition. Let's fix that:
(define (bar a) ;define a function "bar" which takes one argument called "a"
(define (foo x) ;define "foo" as an inner function of bar
(* x a)) ;foo returns x * a
foo) ;bar returns the function foo
Now, bar
returns the function foo
. We have just turned bar
into a higher-order function, because it returns another function.
But there's a problem. Normally, when bar
returns, the local variable a
is no longer needed, and its value is lost. But foo
still makes a reference to a
! So a
needs to stick around for a while longer. This is where closures come in. The value of a
is "closed over", so it sticks around for as long as the function foo
exists. That way, we can call foo
even after bar
has finished execution. Now, let's rename foo
and bar
to see why this is useful:
(define (make-multiplier a)
(define (multiplier x)
(* x a))
multiplier)
Now, we can do this:
(define triple (make-multiplier 3)) ;call make-multiplier with the value 3. Bind the function which is returned to the variable "triple."
(triple 5) ;call triple with the value 5. Since the 3 was "closed over", (triple 5) returns 5 * 3, which is 15.
So - when a function has a "free variable", a closure is created for the function which "closes over" the free variable and preserves it for the lifetime of the function. That way, when the function is passed around and leaves the context it was defined in, the "free variable" continues to be valid.
Upvotes: 1
Reputation: 1
closures are the mean in ML (or in Ocaml, Scheme, Lisp) to implement functions. So all functions are closures (that is, a mix of code and data). For example (using Ocaml syntax)
(* function making an incrementer, returning a function *)
let make_incr i = fun x -> x + i;;
(* use it to define the successor function *)
let succ = make_incr 1;;
(* compute the successor of 4 *)
succ 4;;
The ocaml interpreter is of course answering successfully
val make_incr : int -> int -> int = <fun>
val succ : int -> int = <fun>
- : int = 5
you see that make_incr is a high-order function: given some integer it produces a new function. So given 1 it produces succ
in the above example. And succ
contains both the addition code, and the integer 1. So it mixes code & data (the environment for closed variables) in a closure.
Read more Wikipedia's entry on closure, and any good textbook: the SICP (by Sussman), or C.Queinnec Lisp in Small Pieces, or any good introductory book on Ocaml, or Appel's book "Compiling with Continuations", etc.
Upvotes: 0