Sunday, January 04, 2009

Currying For The Working Programmer - Why Bother?

Grant asked a good question - why should the working programmer care about Currying?

Here's my answer...


For me, the practical side of currying has to do with providing some arguments to a function, while leaving others unset*. The result is a new function that takes in just the unset arguments. This may not sound very useful, but as the example below attempts to show, it can actually be quite handy.

My suggestion is to read up on SRFI 26 and make it one of the standard tools you use.

#lang scheme

;; Scheme doesn't offer automatic currying, but SRFI-26 provides
;; the (cut ...) operator which gets us close enough.
(require srfi/26)

;; A function to send e-mail. This is a bogus implementation,
;; could easily be a "real" implementation
(define (send-mail smtp-host smtp-port from to subject message)
  (printf "Would have sent a message: \n")
  (printf "  SMTP server: ~a:~a\n" smtp-host smtp-port)
  (printf "  From: ~a\n" from)
  (printf "  To: ~a\n" to)
  (printf "  Subject: ~a\n" subject)
  (printf "  Message: ~a\n" message))

;; A place holder function to return a list of e-mail addresses. Used
;; for testing below.
(define (list-of-emails)
  '("foo@nowhere.com" "bar@nowhere.com" "baz@nowhere.com"))

;; Let's start currying...

;; EXAMPLE 1
;; Here we provide two arguments to our send-mail function, and leave
;; 3 unset (<> serves as a place holder). The result of this is a those
;; function that takes in just  3 remaining arguments.
;;
;; Now we can use `mailer' throughout our application and not have to worry about providing
;; the SMTP host and port.
(define mailer (cut send-mail "mail.myhost.com" 25 "noreply@myhost.com" <> <> <>))

;; Use our mailer
(mailer "example@nowhere.com" "Hello World" "This is just a test...")

;; EXAMPLE 2
;; Let's say we want to send the same message over and over again. We can use (cut ...) to
;; provide every argument but the to address.  Notice how we can operate on our already curried
;; function.
(define broadcaster (cut mailer <> "A Special offer, Just For You" "Here's a special offer, sent only to you. We promise..."))

;; Use our broadcaster
(for-each broadcaster
          (list-of-emails))

;; EXAMPLE 3
;; We can provide all the arguments except for a message, and turn our mail 
;; function into a logger.
(define logger (cut mailer "logging@nowhere.com" "Log Message" <>))

;; Pretend this is some complicated function
(define (something-complicated logger)
  (logger "Starting to do something complicated...")
  (void)
  (logger "Whew, and we're done."))
(something-complicated logger)

*Note: true currying involves converting a N argument function into a series of 1 argument functions. So, the example I provieded isn't currying in the formal sense. But, hey, I happen to think it's close enough.

Update: Thanks to Jens Axel for reminding me that I left various < and > improperly escaped in the post, thereby making it impossible to cut and paste. D'oh.

No comments:

Post a Comment