A Practical Use Case for Function Capture in Gleam
A common question for Gleam newcomers is, “What is the use of function captures?”
It’s very likely that you’ve already used it if you’ve built a basic Wisp web app. Here’s an example:
pub fn main() {
let ctx = Context(static_directory: static_directory())
let handler = handle_request(_, ctx)
let assert Ok(_) =
wisp.mist_handler(handler, "secret_key")
|> mist.new
|> mist.port(8000)
|> mist.start_http
process.sleep_forever()
}
pub fn handle_request(req, ctx) {
// ...
}
In this example, we’re passing handler
to the first parameter of wisp.mist_handler
.
See how we’re passing ctx
to the second argument of handle_request
, even though wisp.mist_handler
expects a function with a single parameter, which is req
.
Again, mist_handler
expects this for its first parameter:
fn request_handler(req) {}
And yet, we were able to give it this:
fn request_handler(req, ctx) {}
How were we able to pass a second parameter (ctx
) while it only can accept a single parameter (req
)?
That’s because of the magic of function captures.
Function captures in Gleam are nothing more than syntactic sugar for partial application in functional programming.
// Writing this
let add_1 = add(_, 1)
// is the same as this
let add_1 = fn(a) { add(a, 1) }
Partial application is just a technique to apply some arguments (not all) to a specific function.
For example, if I gave you this function:
fn add(a, b) {
a + b
}
And I asked you to convert it into another function that requires only the a
argument and always uses 1
for b
, how would you do it?
We can do this by defining a new function that calls that function with b=1
, like this:
fn add_1(a) {
// Calling original `add` function here.
// We are passing b as 1.
add(a, 1)
}
Now I can use add_1
instead of add
, where b
is always 1
. So, I only need to pass a
.
add_1(4) // 5 because a=4 and b=1
Notice anything similar to the first example in this article?
It’s the same case as the handler function for wisp.mist_handler
. It only expects a single parameter, yet we were able to send it another parameter: ctx
. Let me show you how we were able to do that.
So, let’s apply what we learned in the add_1
example.
The handler function that wisp.mist_handler
expects is this:
fn request_handler(req) {}
But we want to pass ctx
as the second parameter, like this:
fn request_handler(req, ctx) {}
We need to use partial application to convert fn request_handler(req, ctx) {}
to fn request_handler(req) {}
.
We can do that by wrapping the original function call with another function that takes a single parameter. (By the original function, I mean request_handler(req, ctx) {}
)
fn request_handler(req) {
our_request_handler(req, ctx)
}
Now, if we pass request_handler
to wisp.mist_handler
, it will work because it expects a single-parameter function, and we are providing exactly that.
Since Gleam is a beautiful, concise language, it provides us with a shorthand for this. So, we can rewrite it like this:
let request_handler = our_request_handler(_, ctx)
And that’s the perfect use case for function captures in Gleam.