Conditional expressions

Typed Racket provides a number of special syntactic forms for testing conditions. These are similar to the mechanisms found in Racket and are described in detail in Chapter 4 of the text.

Cond expressions

In Typed Racket, we usually use the cond construct to specify conditional code. Its basic form is

(cond [pred action] [pred action] ...)

where pred is a predicate expression, that has Boolean type, and action is an expression to evaluate when the predicate holds. The last predicate can be the reserved word else.

The evaluation rules for conditionals are

(cond [#t action] …​)

action

(cond [#f action1] [pred2 action2] …​)

(cond [pred2 action2] …​)

(cond [else action])

action

For example, we could define the absolute-value function as

(: absolute-value : Real -> Real)
(define (absolute-value x)
  (cond
    [(< x 0) (- x)]
    [else x]))

There is a subtle type-checking issue when using conditionals. The actions of a conditional must all have the same type. The problem arises when a cond expression is incomplete. In that situation, the compiler adds an extra else clause to complete the cond, but the type of the action is Void (more about Void later), which will likely conflict with the types of the actions that you wrote. For example, consider the following two Typed Racket function definitions:

(: f : Boolean -> String)
(define (f b)
  (cond
    [(eq? b #f) "false"]
    [(eq? b #t) "true"]))


(: g : Integer -> String)
(define (g i)
  (cond
    [(= i 0) "false"]
    [(= i 1) "true"]))

The first one (f) has a complete cond and will pass the type checker, but the second (g) has an incomplete cond (e.g., what if the argument is 2?) and will produce the type error.

Type Checker: type mismatch
  expected: String
  given: Void in: (cond ((= i 0) "false") ((= i 1) "true"))

We can fix this problem by either converting the last predicate to else (assuming that that makes sense for the function) or by adding our own else clause.

(: g : Integer -> String)
(define (g i)
  (cond
    [(= i 0) "false"]
    [(= i 1) "true"]
    [else "error"]))

Unfortunately, there will be situations where the compiler is unable to determine that a cond expression is, in fact, complete as in the following example:

(: h : Boolean -> String)
(define (h b)
  (cond
    [(eq? (not b) #t) "false"]
    [(eq? b #t) "true"]))

There is a tradition in Lisp-family languages, like Racket, that any value that is not #f (false) is considered true. Thus, one can write conditional expressions like

(cond
  ["hi" 1]
  [else 2])

and Typed Racket will not complain. This expression happens to evaluate to 1, since the string "hi" is not false. For this class, you should always use Boolean-valued predicates in your conditional expressions.

If expressions

A more compact way to write a complete cond of two clauses is to use the if form, which has the syntax

(if pred then-action else-action)

and is equivalent to the cond expression

(cond
  [pred then-action]
  [else else-action])

Logical connectives

Typed Racket also provides the standard two logical connectives — and and or — for writing conditional expressions. These have the standard logical definition as defined by the following table

a b (and a b ) (or a b )

#f

#f

#f

#f

#f

#t

#f

#t

#t

#f

#f

#t

#t

#t

#t

#t

There are two differences, however, from the standard binary connectives found in logic. First, like the arithmetic operators, and and or can be used on an arbitrary number of arguments. Second, they are non-strict in their arguments. In other words, they will short circuit evaluation when possible. The following evaluation rules define their semantics:

(and)

#t

(and #f exp …​)

#f

(and #t exp …​)

(and exp …​)

(or)

#f

(or #t exp …​)

#t

(or #f exp …​)

(or exp …​)

Reporting errors

Typed Racket has a function error that we can use to report errors. This function has the following (simplified) type

(: error : String -> Nothing)

(the return type of Nothing signifies that it does not return). The convention is to pass a string that describes the error, often beginning with the name of the enclosing function followed by a colon. For example

(: fact : Integer -> Integer)
;; compute the factorial of n; signal an error on negative inputs
(define (fact n)
  (cond
    [(<= n 0) (error "fact: expected positive argument")]
    [(= n 0) 1]
    [else (* n (fact (- n 1)))]))

If we pass fact a negative argument, we will get an error message as in the following REPL interaction:

> (fact -2)
  fact: expected positive argument

We can test the error handling of functions using check-error, which takes the expression to evaluate and the expected error message as arguments. For example

(check-error (fact -2) "fact: expected positive argument")

In stock Typed Racket, the error function allows a number of alternative argument formats. We have simplified it for the purposes of this course.

Last updated 2019-12-29 10:59:14 -0600
Table of Contents | Back to CS151 Home