Sludges:   Extensions to Signatures for Ht  DP Student Languages
1 Signatures
2 Named Type Definitions
define-type
one-of
3 Typed Struct Definitions
define-struct
define-struct/  typed
4 Predefined Signatures
Image
Posn
Posn  Of
Mouse  Event
Key  Event
List
Maybe
Vector
Vector  Of
Void
5 Intervals
5.1 Integer Intervals
integer-from-to
integer-from
integer-to
5.2 Real-Number Intervals
number-from-to
number-from<-to
number-from-<to
number-from<-<to
number-from
number-from<
number-to
number-<to
6 Design Recipe Helpers
define-header
define-template
7 Student Language Signatures
7.1 Signature Declarations
:
signature
7.2 Signature Forms
enum
mixed
List  Of
predicate
7.3 Predefined Signatures
Any
Boolean
Char
Cons  Of
Empty  List
False
Integer
Rational
Real
String
Symbol
True
8 Configuration
max-signature-violations
signature-violation-dedup
9.3

Sludges: Extensions to Signatures for HtDP Student Languages🔗ℹ

 (require sludges) package: sludges

The sludges teachpack extends the built-in signature system of HtDP student languages. It provides named type definitions, typed struct definitions, predefined signatures for common types, interval signatures for interval ranges, and helpers useful while following the design recipe.

Load it in your program with:

(require sludges)

    1 Signatures

    2 Named Type Definitions

    3 Typed Struct Definitions

    4 Predefined Signatures

    5 Intervals

      5.1 Integer Intervals

      5.2 Real-Number Intervals

    6 Design Recipe Helpers

    7 Student Language Signatures

      7.1 Signature Declarations

      7.2 Signature Forms

      7.3 Predefined Signatures

    8 Configuration

1 Signatures🔗ℹ

A signature denotes a data type (a set of values) and is specified as a signature form, a special syntax that can only used in : signature declarations, inside signature expressions, or in sludges’s define-type definitions.

In a signature form, any name starting with % is a signature variable that stands for any signature data type. Signature variables are not enforced: they accept any value without checking, behaving like Any. Their purpose is documentation only. For example, the following code does not raise any signature violation.

(: same (%T -> %T))
(define (same x) #false)
 
(check-expect (same 7) #false)

2 Named Type Definitions🔗ℹ

syntax

(define-type name signature-form)

(define-type (name param ...) signature-form)
Defines a named signature.

The first form binds name to the signature described by signature-form:

(define-type StringOrFalse (one-of String False))
 
(: X PossiblyString)
(define X "hello")

The second form defines name as a function that takes signature parameters and returns a new signature:

(define-type (Either A B) (one-of A B))
 
(: string->number/maybe (String -> (Either Number String)))

define-type can also describe recursive types:

(define-type List<Number> (one-of Empty (ConsOf Number NumberList)))

Available in: all student languages.

syntax

(one-of signature-form ...)

An alternative name for mixed.

(define-type NumberOrBool (one-of Number Boolean))

Available in: all student languages.

3 Typed Struct Definitions🔗ℹ

syntax

(define-struct name [(field-name sig-form) ...])

(define-struct name [field-name ...])
Defines a struct and a type based on it. The first form uses typed field specifications; the second is the standard untyped form.

All field specifications must use the same style: either all typed or all untyped. Square brackets and parentheses are interchangeable.

Typed form. When every field is written as a (field-name sig-form) pair, sludges adds signature checking to all generated procedures:

(define-struct person [(first String) (last String)])
 
(make-person "Homer" "Simpson")   ; OK
(make-person "Marge" 'Simpson)    ; signature violation: expected a String

This defines:

  • make-person as a constructor with signature String String -> Person. Passing a wrong-typed argument reports a signature violation.

  • person? as a predicate with signature Any -> Boolean.

  • person-first, person-last as selectors with signatures Person -> String.

  • Person as a signature matching any valid instance of struct person.

  • PersonOf as a parametric signature constructor: (PersonOf Sig1 Sig2) matches person values whose fields satisfy Sig1 and Sig2 respectively.

Untyped form. When each field is a plain identifier, define-struct behaves as the built-in define-struct.

(define-struct Pair [a b])
 
(make-pair 3 4)       ; OK
(make-pair 3 "four")  ; OK

This defines:

  • make-pair as a constructor with signature Any Any -> Pair.

  • pair? as a predicate with signature Any -> Boolean.

  • pair-a, pair-b as selectors with signatures Pair -> Any.

  • Pair as a signature matching any valid instance of struct pair.

  • PairOf as a parametric signature constructor: (PairOf Sig1 Sig2) matches pair instances whose fields satisfy Sig1 and Sig2 respectively.

Violations of signatures involving untyped structs are actually detected by the student languages’ regular runtime error checking, not by signature checking. For example, evaluating (pair-first 3) gives the error pair-first: expects a pair, given 3, which is not a signature violation.

Naming convention. The generated signature names use TitleCase with hyphens removed. That is, the struct name is capitalized, hyphens are removed, and the first letter following an hyphen is capitalized. For example, a struct called foo-bar-3baz produces signatures FooBar3Baz and FooBar3BazOf.

Available in: all student languages. In BSL and BSL+, constructors and selectors are first-order. In ASL, fields are mutable.

syntax

(define-struct/typed name ((field-name sig-form) ...))

Equivalent to define-struct with typed fields. Prefer define-struct in new code.

Available in: all student languages.

4 Predefined Signatures🔗ℹ

value

Image : signature?

A signature for an image produced by the 2htdp/image library.

(: BACKGROUND Image)
(define BACKGROUND (empty-scene 400 300))

Available in: all student languages.

value

Posn : signature?

A signature for a posn value.

(: ORIGIN Posn)
(define ORIGIN (make-posn 0 0))

Available in: all student languages.

procedure

(PosnOf x-sig y-sig)  signature?

  x-sig : signature?
  y-sig : signature?
A signature for a posn whose posn-x field satisfies x-sig and whose posn-y field satisfies y-sig.

(: move/x (Integer (PosnOf Integer Integer) -> (PosnOf Integer Integer)))
(define (move/x dx p)
  (make-posn (+ dx (posn-x p)) (posn-y p)))

Available in: all student languages.

value

MouseEvent : signature?

A signature for strings that characterize mouse events, as used by 2htdp/universe.

Available in: all student languages.

value

KeyEvent : signature?

A signature for strings that characterize keyboard events, as used by 2htdp/universe.

Available in: all student languages.

value

List : signature?

A signature for any list. Equivalent to "ListOf Any".

(: STUFF List)
(define STUFF (list 1 "two" #true))

Available in: all student languages.

procedure

(Maybe sig)  signature?

  sig : signature?
A signature for either a value satisfying sig or #false.

(: find (String (ListOf String) -> (Maybe Natural)))

Available in: all student languages.

value

Vector : signature?

A signature for a vector.

Available in: ASL.

procedure

(VectorOf sig)  signature?

  sig : signature?
A signature for a vector whose elements satisfy sig.

(: GRADES (VectorOf Number))
(define GRADES (vector 7.0 8.5 9.5))

Available in: ASL.

value

Void : signature?

A signature for the result of set!, vector-set!, as well as (void) in student languages.

(: reset! (-> Void))
(define (reset!) (set! COUNTER 0))

Available in: ASL.

5 Intervals🔗ℹ

Intervals describe numeric ranges. A < in the name indicates an open (exclusive) endpoint; absence of < means closed (inclusive).

For technical reasons of how signatures are implemented in the student languages, interval signatures are currently only usable within define-type.

(define-type Int-3-5 (integer-from-to 3 5))
(: M Int-3-5)
(define M 4)
 
 
(: N (integer-from-to 3 5))
(define N 4)

5.1 Integer Intervals🔗ℹ

Integer bounds are always inclusive, since bounded integer intervals are always closed.

procedure

(integer-from-to lo hi)  signature?

  lo : exact-integer?
  hi : exact-integer?
Signature for integers in the closed interval [lo, hi]. lo must be less than or equal to hi.

(: DICE (integer-from-to 1 6))

Available in: all student languages.

procedure

(integer-from lo)  signature?

  lo : exact-integer?
Signature for integers greater than or equal to lo: [lo, +inf).

(define-type PosInts (integer-from 1))

Available in: all student languages.

procedure

(integer-to hi)  signature?

  hi : exact-integer?
Signature for integers less than or equal to hi: (-inf, hi].

(define-type NegInts (integer-to -1))

Available in: all student languages.

5.2 Real-Number Intervals🔗ℹ

Bounded intervals take two arguments. A < immediately before or after the hyphen signals an open endpoint.

procedure

(number-from-to lo hi)  signature?

  lo : real?
  hi : real?
Signature for (real) numbers in [lo, hi].

Available in: all student languages.

procedure

(number-from<-to lo hi)  signature?

  lo : real?
  hi : real?
Signature for (real) numbers in (lo, hi].

Available in: all student languages.

procedure

(number-from-<to lo hi)  signature?

  lo : real?
  hi : real?
Signature for (real) numbers in [lo, hi).

Available in: all student languages.

procedure

(number-from<-<to lo hi)  signature?

  lo : real?
  hi : real?
Signature for (real) numbers in (lo, hi).

Available in: all student languages.

Unbounded intervals take a single argument.

procedure

(number-from lo)  signature?

  lo : real?
Signature for (real) numbers in [lo, +inf).

Available in: all student languages.

procedure

(number-from< lo)  signature?

  lo : real?
Signature for (real) numbers in (lo, +inf).

Available in: all student languages.

procedure

(number-to hi)  signature?

  hi : real?
Signature for (real) numbers in (-inf, hi].

Available in: all student languages.

procedure

(number-<to hi)  signature?

  hi : real?
Signature for (real) numbers in (-inf, hi).

Available in: all student languages.

6 Design Recipe Helpers🔗ℹ

The design of a function fun according to the HtDP design recipe follows an incremental process, which produces successive versions of fun that add details in steps. The three main artifacts are:

  1. The header: a stub implementation of fun that does nothing other than returning a default value of the expected type.

  2. The template: an incomplete implementation of fun that captures the structure of the input types.

  3. The implementation: a complete implementation of fun, obtained by filling in the template’s missing parts and rearranging its elements.

Since all three artifacts are definitions for the same function fun, one has to comment out the header before being able to define the template, and to comment out the template before being able to define the implementation.

The helpers define-header and define-template simply allow one to leave headers and templates in the code without commenting them out, as demonstrated in this example.

(: count (List -> Natural))
 
; effectively ignored
(define-header (count lst) 0)
 
; effectively ignored
(define-template (count lst)
  (cond
    [(empty? lst) ...]
    [else (... (first lst) (count (rest lst)) ...)]))
 
(define-template (count lst)
  (cond
    [(empty? lst) 0]
    [else (+ 1 (count (rest lst)))]))

syntax

(define-header (name arg ...) body)

Expands to (void) at run time, so that the subsequent define of the same function does not cause a duplicate definition error.

Available in: all student languages.

syntax

(define-template (name arg ...) body)

Expands to (void) at run time, so that the subsequent define of the same function does not cause a duplicate definition error.

Available in: all student languages.

7 Student Language Signatures🔗ℹ

Signature declarations and signature forms are part of the HtDP student languages: see their documentation in BSL, BSL+, ISL, ISL+, and ASL. Their main features are repeated here for reference, since sludges extends them.

7.1 Signature Declarations🔗ℹ

syntax

(: name signature-form)

Attaches signature-form to the definition of name. There must be a definition of name somewhere in the program.

(: AGE Integer)
(define AGE 42)
 
(: area-of-square (Number -> Number))
(define (area-of-square len)
  (sqr len))

When running the program, Racket checks whether the signatures attached with : actually match the values of the variables. If they don’t, it reports a signature violation along with test failures. A signature violation does not stop the running program.

syntax

(signature signature-form)

Returns the signature described by signature-form as a value.

7.2 Signature Forms🔗ℹ

The student languages and sludges define several signature forms that denote the most common atomic types available in the student languages and its teachpacks. They are described in Predefined Signatures and in Predefined Signatures respectively.

Users can also define new signature types using the following special forms:

syntax

(input-signature-form ... -> output-signature-form)

Describes a function. The inputs are described by the input-signature-forms and the output by output-signature-form.

(: double (Number -> Number))
(define (double x) (* 2 x))

syntax

(enum expr ...)

Describes an enumeration of specific values.

(: cute? ((enum "cat" "snake") -> Boolean))
(define (cute? pet)
  (cond [(string=? pet "cat") #true]
        [(string=? pet "snake") #false]))

syntax

(mixed signature-form ...)

Describes mixed data (an itemization) where each case is described by a signature-form.

(define-type MaybeNumber (mixed Number Boolean))

syntax

(ListOf signature-form)

Describes a list whose elements are described by signature-form.

(: my-list (ListOf Number))
(define my-list (list 1 2 3))

syntax

(predicate expression)

Describes values through a predicate: expression must evaluate to a one-argument function returning a boolean. The signature matches all values for which the predicate returns #true.

(: x (predicate positive?))
(define x 42)

7.3 Predefined Signatures🔗ℹ

value

Any : signature?

A signature for any value.

value

Boolean : signature?

A signature for booleans.

value

Char : signature?

A signature for characters (single-character strings).

procedure

(ConsOf first-sig rest-sig)  signature?

  first-sig : signature?
  rest-sig : signature?
A signature for a cons pair.

value

EmptyList : signature?

A signature for empty lists.

value

False : signature?

A signature for the Boolean value #false.

value

Integer : signature?

A signature for integer numbers.

value

Rational : signature?

A signature for rational numbers.

value

Real : signature?

A signature for real numbers.

value

String : signature?

A signature for strings.

value

Symbol : signature?

A signature for symbols.

value

True : signature?

A signature for the Boolean value #true.

8 Configuration🔗ℹ

Module sludges includes two parameters that control how signature violations are reported. They are documented in this section.

Signature violations are logged during execution, and reported at the end together with test outcomes. Since it is common that several violations have the same root cause, sludges does not report all signature violations by default. Instead, it groups them into buckets according to the criterion signature-violation-dedup, and only reports the first max-signature-violations in each bucket.

parameter

(max-signature-violations)  (or/c exact-positive-integer? #f)

(max-signature-violations n)  void?
  n : (or/c exact-positive-integer? #f)
 = 1
Controls how many signature violations per bucket are shown before further violations in the same bucket are ignored.

  • 1 (the default): only the first violation per bucket is shown.

  • A positive integer n: up to n violations per bucket.

  • #f: no limit; all violations are shown. This is the original behavior in the student languages.

parameter

(signature-violation-dedup)  symbol?

(signature-violation-dedup mode)  void?
  mode : symbol?
 = 'signature
Controls how signature violations are grouped into buckets.

  • 'signature (the default): one bucket per (signature name, signature object) pair.

  • 'type-name: one bucket per signature name. This is the most coarse grouping criterion.

  • 'object: one bucket per (signature name, violating object) pair.

Here is an example of how different grouping modes work:

(max-signature-violations 1)
 
(define-type StringOrFalse (one-of String False))
 
(: S1 StringOrFalse)
(define S1 1)
 
(: S2 StringOrFalse)
(define S2 2)
 
(: S3 StringOrFalse)
(define S3 1)
  • If signature-violation-dedup is 'type-name, one signature violation (the first one, involving S1) is reported, because all violations are of the same StringOrFalse.

  • If signature-violation-dedup is 'signature, all three signature violations are reported, because each violates a different signature (defined with :).

  • If signature-violation-dedup is 'object, two signature violations are reported. The third one (involving S3) is ignored because the value/object that violates S3’s signature is 1, the same as the one that violates S1’s signature.