racket/collects/scribblings/htdp-langs/beginner.scrbl
Jay McCarthy 01a41a812e Closing pr11216. Adding one armed check-error to teaching languages.
[It will not bother me if we revert this commit. I liked SK's idea and found it easy to implement. I wonder if others will be worried that it is easy to unintentionally leave off the second argument to check-error. I also wonder if it is problematic to add new string constants, like I've done.]

Here is an example:

(check-error (/ 1 0) "/: division by zero")
(check-error (/ 1 0) "divide by zero")
(check-error (/ 1 0))
(check-error 1)

Here is the output:

Ran 4 tests.
2 of the 4 tests failed.

No signature violations.

Check failures:
	check-error encountered the following error instead of the expected divide by zero
   :: /: division by zero
in ex.rkt, line 2, column 0
	check-error expected an error, but instead received the value 1.
in ex.rkt, line 4, column 0
2010-09-16 21:00:05 -06:00

366 lines
13 KiB
Racket

#lang scribble/doc
@(require "common.ss"
"std-grammar.ss"
"prim-ops.ss"
(for-label lang/htdp-beginner))
@title[#:style 'toc #:tag "beginner"]{Beginning Student}
@declare-exporting[lang/htdp-beginner]
@schemegrammar*+library[
#:literals (define define-struct lambda cond else if and or empty true false require lib planet
check-expect check-within check-error)
(check-expect check-within check-error require)
[program (code:line def-or-expr ...)]
[def-or-expr definition
expr
test-case
library-require]
[definition (define (id id id ...) expr)
(define id expr)
(define id (lambda (id id ...) expr))
(define-struct id (id ...))]
[expr (code:line (id expr expr ...) (code:comment @#,seclink["beginner-call"]{function call}))
(code:line (prim-op expr ...) (code:comment @#,seclink["beginner-prim-call"]{primitive operation call}))
(cond [expr expr] ... [expr expr])
(cond [expr expr] ... [else expr])
(if expr expr expr)
(and expr expr expr ...)
(or expr expr expr ...)
empty
id
(code:line id (code:comment @#,seclink["beginner-id"]{identifier}))
(code:line @#,elem{@schemevalfont{'}@scheme[id]} (code:comment @#,seclink["beginner-quote"]{symbol}))
number
true
false
string
character]
]
@|prim-nonterms|
@prim-ops['(lib "htdp-beginner.ss" "lang") #'here]
@; ----------------------------------------------------------------------
@section{@scheme[define]}
@defform[(define (id id id ...) expr)]{
Defines a function. The first @scheme[id] inside the parentheses is
the name of the function. All remaining @scheme[id]s are the names of
the function's arguments. The @scheme[expr] is the body of the
function, evaluated whenever the function is called. The name of the
function cannot be that of a primitive or another definition.}
@defform/none[#:literals (define)
(define id expr)]{
Defines a constant @scheme[id] as a synonym for the value produced by
@scheme[expr]. The defined name cannot be that of a primitive or
another definition, and @scheme[id] itself must not appear in
@scheme[expr].}
@defform/none[#:literals (define lambda)
(define id (lambda (id id ...) expr))]{
An alternate form for defining functions. The first @scheme[id] is the
name of the function. The @scheme[id]s in parentheses are the names of
the function's arguments, and the @scheme[expr] is the body of the
function, which evaluated whenever the function is called. The name
of the function cannot be that of a primitive or another definition.}
@defidform[lambda]{
The @scheme[lambda] keyword can only be used with @scheme[define] in
the alternative function-definition syntax.}
@; ----------------------------------------------------------------------
@section{@scheme[define-struct]}
@defform[(define-struct structid (fieldid ...))]{
Define a new type of structure. The structure's fields are named by
the @scheme[fieldid]s in parentheses. After evaluation of a
define-struct form, a set of new primitives is available for creation,
extraction, and type-like queries:
@itemize[
@item{@schemeidfont{make-}@scheme[structid] : takes a number of
arguments equal to the number of fields in the structure type,
and creates a new instance of the structure type.}
@item{@scheme[structid]@schemeidfont{-}@scheme[fieldid] : takes an
instance of the structure and returns the field named by
@scheme[structid].}
@item{@scheme[structid]@schemeidfont{?} : takes any value, and returns
@scheme[true] if the value is an instance of the structure type.}
@item{@scheme[structid] : an identifier representing the structure
type, but never used directly.}
]
The created names must not be the same as a primitive or another defined name.}
@; ----------------------------------------------------------------------
@section[#:tag "beginner-call"]{Function Calls}
@defform/none[(id expr expr ...)]{
Calls a function. The @scheme[id] must refer to a defined function,
and the @scheme[expr]s are evaluated from left to right to produce the
values that are passed as arguments to the function. The result of the
function call is the result of evaluating the function's body with
every instance of an argument name replaced by the value passed for
that argument. The number of argument @scheme[expr]s must be the same
as the number of arguments expected by the function.}
@defform[(#%app id expr expr ...)]{
A function call can be written with @scheme[#%app], though it's
practically never written that way.}
@; ----------------------------------------------------------------------
@section[#:tag "beginner-prim-call"]{Primitive Calls}
@defform/none[(prim-op expr ...)]{
Like a @seclink["beginner-call"]{function call}, but for a primitive
operation. The @scheme[expr]s are evaluated from left to right, and
passed as arguments to the primitive operation named by
@scheme[prim-op]. A @scheme[define-struct] form creates new
primitives.}
@; ----------------------------------------------------------------------
@section{@scheme[cond]}
@defform[(cond [expr expr] ... [expr expr])]{
A @scheme[cond] form contains one or more ``lines'' that are
surrounded by parentheses or square brackets. Each line contains two
@scheme[expr]s: a question @scheme[expr] and an answer
@scheme[expr].
The lines are considered in order. To evaluate a line, first evaluate
the question @scheme[expr]. If the result is @scheme[true], then the
result of the whole @scheme[cond] expression is the result of
evaluating the answer @scheme[expr] of the same line. If the result of
evaluating the question @scheme[expr] is @scheme[false], the line is
discarded and evaluation proceeds with the next line.
If the result of a question @scheme[expr] is neither @scheme[true] nor
@scheme[false], it is an error. If none of the question @scheme[expr]s
evaluates to @scheme[true], it is also an error.}
@defform/none[#:literals (cond else)
(cond [expr expr] ... [else expr])]{
This form of @scheme[cond] is similar to the prior one, except that
the final @scheme[else] clause is always taken if no prior line's test
expression evaluates to @scheme[true]. In other words, @scheme[else]
acts like @scheme[true], so there is no possibility to ``fall off the
end'' of the @scheme[cond] form.}
@defidform[else]{
The @scheme[else] keyword can be used only with @scheme[cond].}
@; ----------------------------------------------------------------------
@section{@scheme[if]}
@defform[(if expr expr expr)]{
The first @scheme[expr] (known as the ``test'' @scheme[expr]) is
evaluated. If it evaluates to @scheme[true], the result of the
@scheme[if] expression is the result of evaluating the second
@scheme[expr] (often called the ``then'' @scheme[expr]). If the text
@scheme[expr] evaluates to @scheme[false], the result of the
@scheme[if] expression is the result of evaluating the third
@scheme[expr] (known as the ``else'' @scheme[expr]). If the
result of evaluating the test @scheme[expr] is neither @scheme[true]
nor @scheme[false], it is an error.}
@; ----------------------------------------------------------------------
@section{@scheme[and]}
@defform[(and expr expr expr ...)]{
The @scheme[expr]s are evaluated from left to right. If the first
@scheme[expr] evaluates to @scheme[false], the @scheme[and] expression
immediately evaluates to @scheme[false]. If the first @scheme[expr]
evaluates to @scheme[true], the next expression is considered. If all
@scheme[expr]s evaluate to @scheme[true], the @scheme[and] expression
evaluates to @scheme[true]. If any of the expressions evaluate to a
value other than @scheme[true] or @scheme[false], it is an error.}
@; ----------------------------------------------------------------------
@section{@scheme[or]}
@defform[(or expr expr expr ...)]{
The @scheme[expr]s are evaluated from left to right. If the first
@scheme[expr] evaluates to @scheme[true], the @scheme[or] expression
immediately evaluates to @scheme[true]. If the first @scheme[expr]
evaluates to @scheme[false], the next expression is considered. If all
@scheme[expr]s evaluate to @scheme[false], the @scheme[or] expression
evaluates to @scheme[false]. If any of the expressions evaluate to a
value other than @scheme[true] or @scheme[false], it is an error.}
@; ----------------------------------------------------------------------
@section{Test Cases}
@defform[(check-expect expr expr)]{
A test case to check that the first @scheme[expr] produces the same
value as the second @scheme[expr], where the latter is normally an
immediate value.}
@defform[(check-within expr expr expr)]{
Like @scheme[check-expect], but with an extra expression that produces
a number @scheme[_delta]. The test case checks that each number in the
result of the first @scheme[expr] is within @scheme[_delta] of each
corresponding number from the second @scheme[expr].}
@defform*[[(check-error expr expr)
(check-error expr)]]{
A test case to check that the first @scheme[expr] signals an error,
where the error messages matches the string produced by the second
@scheme[expr], if it is present.}
@defform[(check-member-of expr expr expr ...)]{
A test case to check that the first @scheme[expr] produces an element
that is equivalent to one of the following @scheme[expr]s.}
@defform[(check-range expr expr expr)]{
A test case to check that the first @scheme[expr] produces a number
inbetween the numbers produced by the second and third @scheme[expr]s,
inclusive.}
@; ----------------------------------------------------------------------
@section{@scheme[empty]}
@defthing[empty empty?]{
The empty list.}
@; ----------------------------------------------------------------------
@section[#:tag "beginner-id"]{Identifiers}
@defform/none[id]{
An @scheme[id] refers to a defined constant or argument within a
function body. If no definition or argument matches the @scheme[id]
name, an error is reported. Similarly, if @scheme[id] matches the name
of a defined function or primitive operation, an error is reported.}
@; ----------------------------------------------------------------------
@section[#:tag "beginner-quote"]{Symbols}
@deftogether[(
@defform/none[(unsyntax @elem{@schemevalfont{'}@scheme[id]})]
@defform[(quote id)]
)]{
A quoted @scheme[id] is a symbol. A symbol is a constant, like
@scheme[0] and @scheme[empty].
Normally, a symbol is written with a @litchar{'}, like
@scheme['apple], but it can also be written with @scheme[quote], like
@scheme[(@#,scheme[quote] apple)].
The @scheme[id] for a symbol is a sequence of characters not including
a space or one of the following:}
@t{@hspace[2] @litchar{"} @litchar{,} @litchar{'} @litchar{`}
@litchar{(} @litchar{)} @litchar{[} @litchar{]}
@litchar["{"] @litchar["}"] @litchar{|} @litchar{;}
@litchar{#}}
@; ----------------------------------------------------------------------
@section{@scheme[true] and @scheme[false]}
@defthing[true boolean?]{
The true value.}
@defthing[false boolean?]{
The false value.}
@; ----------------------------------------------------------------------
@section{@scheme[require]}
@defform[(require string)]{
Makes the definitions of the module specified by @scheme[string]
available in the current module (i.e., current file), where @scheme[string]
refers to a file relative to the enclosing file.
The @scheme[string] is constrained in several ways to avoid problems
with different path conventions on different platforms: a @litchar{/}
is a directory separator, @litchar{.} always means the current
directory, @litchar{..} always means the parent directory, path
elements can use only @litchar{a} through @litchar{z} (uppercase or
lowercase), @litchar{0} through @litchar{9}, @litchar{-}, @litchar{_},
and @litchar{.}, and the string cannot be empty or contain a leading
or trailing @litchar{/}.}
@defform/none[#:literals (require)
(require module-id)]{
Accesses a file in an installed library. The library name is an
identifier with the same constraints as for a relative-path string,
with the additional constraint that it must not contain a
@litchar{.}.}
@defform/none[#:literals (require lib)
(require (lib string string ...))]{
Accesses a file in an installed library, making its definitions
available in the current module (i.e., current file). The first
@scheme[string] names the library file, and the remaining
@scheme[string]s name the collection (and sub-collection, and so on)
where the file is installed. Each string is constrained in the same
way as for the @scheme[(require string)] form.}
@defform/none[#:literals (require planet)
(require (planet string (string string number number)))]{
Accesses a library that is distributed on the internet via the PLaneT
server, making it definitions available in the current module (i.e.,
current file).}
@; ----------------------------------------
@section[#:tag "beginner-prim-ops"]{Primitive Operations}
@prim-op-defns['(lib "htdp-beginner.ss" "lang") #'here '()]