232: Flexible curried procedures

by Wolfgang Corcoran-Mathe, based on a design by Jason Hemann and Daniel P. Friedman.

Status

This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-232@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

Scheme lacks a flexible way to create and apply curried procedures. This SRFI describes curried, a variant of lambda that creates true curried procedures which also behave just like ordinary Scheme procedures. They can be applied to their arguments one by one, all at once, or anywhere in between, without any novel syntax. curried also supports nullary and variadic procedures, and procedures created with it have predictable behavior when applied to surplus arguments.

Rationale

Currying is a well-known tactic in functional programming, and many languages (e.g. Standard ML and Haskell) provide convenient ways to define curried procedures. True currying, though, is somewhat uncommon in Scheme. Beyond the fundamental building block of first-class procedures, Scheme provides little support for defining curried procedures, and the clumsiness of applying such procedures may deter programmers from using them at all. Some attempts have been made at improving the situation. The “higher-order lambda” notation of SRFI 219 extends the syntax of define with a simple shorthand for writing curried procedures. The complexity of applying curried procedures remains, however:

(define ((add-c x) y)
  (+ x y))

(add-c 2 3) ⇒ ; error: too many arguments

((add-c 2) 3) ⇒ 5

Perhaps because of this clumsiness, Scheme has another form which provides something a little like currying. The cut operator, introduced by SRFI 26, is not a currying operator; instead, it allows procedures to be easily specialized on given parameters. However, this is not too difficult in ordinary Scheme (cf. Al* Petrofsky's comment to the SRFI 26 mailing list), and, as such, cut is not frequently seen in Scheme programs. cut also introduces a novel notation using the tokens <> and <...>, which may pose an obstacle for new users.

The curried form described in this SRFI allows the creation of true curried procedures which can also be treated as normal, uncurried Scheme procedures. For example, a curried procedure of two arguments may be applied to one argument to return a new procedure of one argument, or directly to two arguments to return its value. curried also provides consistent semantics for nullary and variadic procedures, and predictable behavior for cases in which a procedure is applied to more arguments than it accepts. All this is accomplished without the introduction of any novel forms; syntactically, curried is very similar to lambda, which should make it easy to adopt in existing Scheme programs.

This SRFI is not meant to replace SRFIs 26 or 219. In some cases, these existing techniques may be preferable; the SRFI 219 notation does much less "under the table" than does curried, and cut provides greater flexibility in argument specialization. The curried form provides another option, one that will hopefully also prove useful and convenient.

Specification

(curried ⟨formals⟩ ⟨body⟩)

Syntax: ⟨formals⟩ and ⟨body⟩ are as described in section 4.1.4 of the R⁷RS.

Semantics: If ⟨formals⟩ is an empty list, then a curried expression evaluates to ⟨body⟩. Otherwise, it evaluates to a procedure with the same environment semantics as lambda (R⁷RS 4.1.4). The application of a procedure created by curried has the following semantics:

(define-curried (⟨variable⟩ ⟨formals⟩) ⟨body⟩)

Equivalent to

(define ⟨variable⟩
  (curried ⟨formals⟩ ⟨body⟩))

Examples

Simple currying

Here, curried is used to create procedures which can be concisely parameterized:

(define-curried (add* x y) (+ x y))

(map (add* 2) '(1 2 3))
 ⇒ (3 4 5)

(define-curried (fold* proc base lis)
  (fold proc base lis))

(let ((sum (fold* + 0))
      (product (fold* * 1))
      (lis '(1 2 3 4 5)))
  (list (sum lis) (product lis)))
 ⇒ (15 120)

Curried polyvariadic procedures

“Polyvariadic” refers to a procedure denoted by an expression of the form (lambda (v₁ … vₙ . vₙ₊₁) ⟨body⟩). A polyvariadic procedure created with curried can be partially applied until a procedure of at least one argument remains:

(define foo (curried (a b . rest) (list a b rest)))

((foo 1) 2 3 4) ⇒ (1 2 (3 4))

((foo 'a) 'b) ⇒ (a b ())

Extra arguments

When a curried procedure is applied to more arguments than it accepts, the remaining arguments are passed to the procedure body:

((curried (a)
   (curried (b)
     (curried (c) (+ a b c)))) 1 2 3) ⇒ 6

Currying with nullary procedures

Since nullary curried expressions evaluate to their bodies, application “passes through” any number of such expressions:

((curried () (curried (x y) (+ x y))) 2 3)
 ⇒ 5

(((curried (x)
    (curried ()
      (curried (y z)
        (list x (* y z))))) 4 5) 6)
 ⇒ (4 30)

Implementation

The sample implementation is portable to R⁶RS and R⁷RS syntax-rules implementations and can be found in the repository for this SRFI.

Acknowledgements

curried is based heavily on the lambda* form created by Jason Hemann and Daniel P. Friedman and described elegantly in “λ*: Beyond Currying”. Their work is gratefully acknowledged.

Thanks to the SRFI editor and to those who provided feedback on this SRFI via the mailing list or the #scheme IRC channel.

References

Jason Hemann & Daniel P. Friedman, “λ*: Beyond Currying” (2013). Available on the Web.

© 2022 Wolfgang Corcoran-Mathe.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Arthur A. Gleckler