1.6 Definitions

The define form defines an identifier to be a synonym for a value:

> (define pi 3.14)
> pi

- Number

3.14

> (define tau (+ pi pi))
> tau

- Number

6.28

The define form can also define a function. The difference is that define for a function definition is followed by an open parenthesis, then the function name, a name for each argument, and a closing parenthesis. The expression afterward is the body of the function, which can refer to the function arguments and is evaluated when the function is called.

> (define (circle-area r)
    (* pi (* r r)))
> (circle-area 10)

- Number

314.0

Since Getting Started, we have been evaluating forms only in DrRacket’s bottom area, which is also known as the interactions area. Definitions normally go in the top area—which is known as the definitions area, naturally.

Put these two definitions in the definitions area:

(define (is-odd? x)
  (if (zero? x)
      #f
      (is-even? (- x 1))))
 
(define (is-even? x)
  (if (zero? x)
      #t
      (is-odd? (- x 1))))

Click Run. The functions is-odd? and is-even? are now available in the interactions area:

> is-odd?

- (Number -> Boolean)

#<procedure:is-odd?>

> (is-odd? 12)

- Boolean

#f

In our definitions of pi and tau, plait inferred that the newly defined names have type Number and that is-odd? has type (Number -> Boolean). Programs are often easier to read and understand if you write explicitly the type that would otherwise be inferred. Declaring types can sometimes help improve or localize error messages when Plait’s attempt to infer a type fails, since inference can other end up depending on the whole program.

Declare a type for a constant by writing : followed by a type after the defined identifier:

(define groceries : (Listof String) '("milk" "cookies"))

Alternatively, you can declare an idenitifier’s type separate from its definition by using :.

(groceries : (Listof String))
(define groceries '("milk" "cookies"))

The declaration can appear before or after the definition, as long as it is in the same layer of declarations as the definition. You can even have multiple type definitions for the same identifier, and the type checker will ensure that they’re all consistent.

For a function, attach a type to an argument by writing square brackets around the argument name, :, and a type. Write the function’s result type with : and the type after the parentheses that group the function name with its arguments.

(define (starts-milk? [items : (Listof String)]) : Boolean
  (equal? (first items) "milk"))

Or, of course, declare the type separately:

(starts-milk? : ((Listof String) -> Boolean))
(define (starts-milk? items)
  (equal? (first items) "milk"))

You can declare local functions and constants by using the local form as a wrapper. The definitions that appear after local are visible only within the local form, and the result of the local form is the value of the expression that appears after the definitions. The definitions must be grouped with square brackets.

> (local [(define pi-ish 3)
          (define (approx-circle-area r)
            (* pi-ish (* r r)))]
     (approx-circle-area 2))

- Number

12

> pi-ish ; not visible outside the local

eval:123:0: code:line: free variable while typechecking

  in: code:line

> approx-circle-area ; not visible outside the local

eval:124:0: code:line: free variable while typechecking

  in: code:line

The local form is most often used inside a function to define a helper function or to avoid a repeated computating involving the function arguments.

> (define (discard-first-if-fruit items)
    (local [(define a (first items))]
      (cond
       [(equal? a "apple") (rest items)]
       [(equal? a "banana") (rest items)]
       [else items])))
> (discard-first-if-fruit '("apple" "potato"))

- (Listof String)

'("potato")

> (discard-first-if-fruit '("banana" "potato"))

- (Listof String)

'("potato")

> (discard-first-if-fruit '("potato" "apple"))

- (Listof String)

'("potato" "apple")

The let and letrec forms are similar to local, but they are somewhat more compact by avoiding the requirement to write define. The discard-first-if-fruit example above can be equivalently written using let:

(define (discard-first-if-fruit items)
  (let ([a (first items)])
    (cond
     [(equal? a "apple") (rest items)]
     [(equal? a "banana") (rest items)]
     [else items])))