On this page:
32.1 Numbers, Strings, and Booleans
32.2 Infix Expressions
32.3 Function Definition and Application
32.4 Tests
32.5 Variable Names
32.6 Data Definitions
32.7 Conditionals
32.8 Lists
32.9 First-Class Functions
32.10 Annotations
32.11 What Else?

32 Pyret for Racketeers and Schemers

    32.1 Numbers, Strings, and Booleans

    32.2 Infix Expressions

    32.3 Function Definition and Application

    32.4 Tests

    32.5 Variable Names

    32.6 Data Definitions

    32.7 Conditionals

    32.8 Lists

    32.9 First-Class Functions

    32.10 Annotations

    32.11 What Else?

If you’ve programmed before in a language like Scheme or the student levels of Racket (or the WeScheme programming environment), or for that matter even in certain parts of OCaml, Haskell, Scala, Erlang, Clojure, or other languages, you will find many parts of Pyret very familiar. This chapter is specifically written to help you make the transition from (student) Racket/Scheme/WeScheme (abbreviated “RSW”) to Pyret by showing you how to convert the syntax. Most of what we say applies to all these languages, though in some cases we will refer specifically to Racket (and WeScheme) features not found in Scheme.

In every example below, the two programs will produce the same results.

32.1 Numbers, Strings, and Booleans

Numbers are very similar between the two. Like Scheme, Pyret implements arbitrary-precision numbers and rationals. Some of the more exotic numeric systems of Scheme (such as complex numbers) aren’t in Pyret; Pyret also treats imprecise numbers slightly differently.

RSW

Pyret

1

1

RSW

Pyret

1/2

1/2

RSW

Pyret

#i3.14

~3.14

Strings are also very similar, though Pyret allows you to use single-quotes as well.

RSW

Pyret

"Hello, world!"

"Hello, world!"

RSW

Pyret

"\"Hello\", he said"

"\"Hello\", he said"

RSW

Pyret

"\"Hello\", he said"

'"Hello", he said'

Booleans have the same names:

RSW

Pyret

true

true

RSW

Pyret

false

false

32.2 Infix Expressions

Pyret uses an infix syntax, reminiscent of many other textual programming languages:

RSW

Pyret

(+ 1 2)

1 + 2

RSW

Pyret

(* (- 4 2) 5)

(4 - 2) * 5

Note that Pyret does not have rules about orders of precedence between operators, so when you mix operators, you have to parenthesize the expression to make your intent clear. When you chain the same operator you don’t need to parenthesize; chaining associates to the left in both languages:

RSW

Pyret

(/ 1 2 3 4)

1 / 2 / 3 / 4

These both evaluate to 1/24.

32.3 Function Definition and Application

Function definition and application in Pyret have an infix syntax, more reminiscent of many other textual programming languages. Application uses a syntax familiar from conventional algebra books:

RSW

Pyret

(dist 3 4)

dist(3, 4)

Application correspondingly uses a similar syntax in function headers, and infix in the body:

RSW

Pyret

(define (dist x y)
  (sqrt (+ (* x x)
           (* y y))))

fun dist(x, y): num-sqrt((x * x) + (y * y)) end

32.4 Tests

There are essentially three different ways of writing the equivalent of Racket’s check-expect tests. They can be translated into check blocks:

RSW

Pyret

(check-expect 1 1)

check: 1 is 1 end

Note that multiple tests can be put into a single block:

RSW

Pyret

(check-expect 1 1)
(check-expect 2 2)

check: 1 is 1 2 is 2 end

The second way is this: as an alias for check we can also write examples. The two are functionally identical, but they capture the human difference between examples (which explore the problem, and are written before attempting a solution) and tests (which try to find bugs in the solution, and are written to probe its design).

The third way is to write a where block to accompany a function definition. For instance:

fun double(n): n + n where: double(0) is 0 double(10) is 20 double(-1) is -2 end

These can even be written for internal functions (i.e., functions contained inside other functions), which isn’t true for check-expect.

In Pyret, unlike in Racket, a testing block can contain a documentation string. This is used by Pyret when reporting test successes and failures. For instance, try to run and see what you get:

check "squaring always produces non-negatives": (0 * 0) is 0 (-2 * -2) is 4 (3 * 3) is 9 end

This is useful for documenting the purpose of a testing block.

Just as in Racket, there are many testing operators in Pyret (in addition to is). See the documentation.

32.5 Variable Names

Both languages have a fairly permissive system for naming variables. While you can use CamelCase and under_scores in both, it is conventional to instead use what is known as kebab-case.This name is inaccurate. The word “kebab” just means “meat”. The skewer is the “shish”. Therefore, it ought to at least be called “shish kebab case”. Thus:

RSW

Pyret

this-is-a-name

this-is-a-name

Even though Pyret has infix subtraction, the language can unambiguously tell apart this-name (a variable) from this - name (a subtraction expression) because the - in the latter must be surrounded by spaces.

Despite this spacing convention, Pyret does not permit some of the more exotic names permitted by Scheme. For instance, one can write
(define e^i*pi -1)
in Scheme but that is not a valid variable name in Pyret.

32.6 Data Definitions

Pyret diverges from Racket (and even more so from Scheme) in its handling of data definitions. First, we will see how to define a structure:

RSW

Pyret

(define-struct pt (x y))

data Point: | pt(x, y) end

This might seem like a fair bit of overkill, but we’ll see in a moment why it’s useful. Meanwhile, it’s worth observing that when you have only a single kind of datum in a data definition, it feels unwieldy to take up so many lines. Writing it on one line is valid, but now it feels ugly to have the | in the middle:

data Point: | pt(x, y) end

Therefore, Pyret permits you to drop the initial |, resulting in the more readable

data Point: pt(x, y) end

Now suppose we have two kinds of points. In the student languages of Racket, we would describe this with a comment:
;; A Point is either
;; - (pt number number), or
;; - (pt3d number number number)
In Pyret, we can express this directly:

data Point: | pt(x, y) | pt3d(x, y, z) end

In short, Racket optimizes for the single-variant case, whereas Pyret optimizes for the multi-variant case. As a result, it is difficult to clearly express the multi-variant case in Racket, while it is unwieldy to express the single-variant case in Pyret.

For structures, both Racket and Pyret expose constructors, selectors, and predicates. Constructors are just functions:

RSW

Pyret

(pt 1 2)

pt(1, 2)

Predicates are also functions with a particular naming scheme:

RSW

Pyret

(pt? x)

is-pt(x)

and they behave the same way (returning true if the argument was constructed by that constructor, and false otherwise). In contrast, selection is different in the two languages (and we will see more about selection below, with cases):

RSW

Pyret

(pt-x v)

v.x

Note that in the Racket case, pt-x checks that the parameter was constructed by pt before extracting the value of the x field. Thus, pt-x and pt3d-x are two different functions and neither one can be used in place of the other. In contast, in Pyret, .x extracts an x field of any value that has such a field, without attention to how it was constructed. Thus, we can use .x on a value whether it was constructed by pt or pt3d (or indeed anything else with that field). In contrast, cases does pay attention to this distinction.

32.7 Conditionals

There are several kinds of conditionals in Pyret, one more than in the Racket student languages.

General conditionals can be written using if, corresponding to Racket’s if but with more syntax.

RSW

Pyret

(if full-moon
    "howl"
    "meow")

if full-moon: "howl" else: "meow" end

RSW

Pyret

(if full-moon
    "howl"
    (if new-moon
        "bark"
        "meow"))

if full-moon: "howl" else if new-moon: "bark" else: "meow" end

Note that if includes else if, which makes it possible to list a collection of questions at the same level of indentation, which if in Racket does not have. The corresponding code in Racket would be written
(cond
  [full-moon "howl"]
  [new-moon "bark"]
  [else "meow"])
to restore the indentation. There is a similar construct in Pyret called ask, designed to parallel cond:

ask: | full-moon then: "howl" | new-moon then: "bark" | otherwise: "meow" end

In Racket, we also use cond to dispatch on a datatype:
(cond
  [(pt? v)   (+ (pt-x v) (pt-y v))]
  [(pt3d? v) (+ (pt-x v) (pt-z v))])
We could write this in close parallel in Pyret:

ask: | is-pt(v) then: v.x + v.y | is-pt3d(v) then: v.x + v.z end

or even as:

if is-pt(v): v.x + v.y else if is-pt3d(v): v.x + v.z end

(As in Racket student languages, the Pyret versions will signal an error if no branch of the conditional matched.)

However, Pyret provides a special syntax just for data definitions:

cases (Point) v: | pt(x, y) => x + y | pt3d(x, y, z) => x + z end

This checks that v is a Point, provides a clean syntactic way of identifying the different branches, and makes it possible to give a concise local name to each field position instead of having to use selectors like .x. In general, in Pyret we prefer to use cases to process data definitions. However, there are times when, for instance, there many variants of data but a function processes only very few of them. In such situations, it makes more sense to explicitly use predicates and selectors.

32.8 Lists

In Racket, depending on the language level, lists are created using either cons or list, with empty for the empty list. The corresponding notions in Pyret are called link, list, and empty, respectively. link is a two-argument function, just as in Racket:

RSW

Pyret

(cons 1 empty)

link(1, empty)

RSW

Pyret

(list 1 2 3)

[list: 1, 2, 3]

Note that the syntax [1, 2, 3], which represents lists in many languages, is not legal in Pyret: lists are not privileged with their own syntax. Rather, we must use an explicit constructor: just as [list: 1, 2, 3] constructs a list, [set: 1, 2, 3] constructs a set instead of a list.In fact, we can create our own constructors and use them with this syntax.

Exercise

Try typing [1, 2, 3] and see the error message.

This shows us how to construct lists. To take them apart, we use cases. There are two variants, empty and link (which we used to construct the lists):

RSW

Pyret

(cond
  [(empty? l) 0]
  [(cons? l)
   (+ (first l)
      (g (rest l)))])

cases (List) l: | empty => 0 | link(f, r) => f + g(r) end

It is conventional to call the fields f and r (for “first” and “rest”). Of course, this convention does not work if there are other things by the same name; in particular, when writing a nested destructuring of a list, we conventionally write fr and rr (for “first of the rest” and “rest of the rest”).

32.9 First-Class Functions

The equivalent of Racket’s lambda is Pyret’s lam:

RSW

Pyret

(lambda (x y) (+ x y))

lam(x, y): x + y end

32.10 Annotations

In student Racket languages, annotations are usually written as comments:
; square: Number -> Number
; sort-nums: List<Number> -> List<Number>
; sort: List<T> * (T * T -> Boolean) -> List<T>
In Pyret, we can write the annotations directly on the parameters and return values. Pyret will check them to a limited extent dynamically, and can check them statically with its type checker. The corresponding annotations to those above would be written as

fun square(n :: Number) -> Number: ... fun sort-nums(l :: List<Number>) -> List<Number>: ... fun sort<T>(l :: List<T>, cmp :: (T, T -> Boolean)) -> List<T>: ...

Though Pyret does have a notation for writing annotations by themselves (analogous to the commented syntax in Racket), they aren’t currently enforced by the language, so we don’t include it here.

32.11 What Else?

If there are other parts of Scheme or Racket syntax that you would like to see translated, please let us know.