#lang scribble/doc @(require scribble/manual scribble/eval scribble/bnf "guide-utils.ss") @(define ex-eval (make-base-eval)) @title[#:tag "syntax-overview"]{Simple Definitions and Expressions} A program module is written as @schemeblock[ #, @BNF-seq[@litchar{#lang} @nonterm{langname} @kleenestar{@nonterm{topform}}] ] where a @nonterm{topform} is either a @nonterm{definition} or an @nonterm{expr}. The @tech{REPL} also evaluates @nonterm{topform}s. In syntax specifications, text with a gray background, such as @litchar{#lang}, represents literal text. Whitespace must appear between such literals and nonterminals like @nonterm{id}, except that whitespace is not required before or after @litchar{(}, @litchar{)}, @litchar{[}, or @litchar{]}. A comment, which starts with @litchar{;} and runs until the end of the line, is treated the same as whitespace. Following the usual conventions, @kleenestar{} in a grammar means zero or more repetitions of the preceding element, @kleeneplus{} means one or more repetitions of the preceding element, and @BNF-group{} groups a sequence as an element for repetition. @(define val-defn-stx @BNF-seq[@litchar{(}@litchar{define} @nonterm{id} @nonterm{expr} @litchar{)}]) @(define fun-defn-stx @BNF-seq[@litchar{(}@litchar{define} @litchar{(} @nonterm{id} @kleenestar{@nonterm{id}} @litchar{)} @kleeneplus{@nonterm{expr}} @litchar{)}]) @(define fun-defn2-stx @BNF-seq[@litchar{(}@litchar{define} @litchar{(} @nonterm{id} @kleenestar{@nonterm{id}} @litchar{)} @kleenestar{@nonterm{definition}} @kleeneplus{@nonterm{expr}} @litchar{)}]) @(define app-expr-stx @BNF-seq[@litchar{(} @nonterm{id} @kleenestar{@nonterm{expr}} @litchar{)}]) @(define app2-expr-stx @BNF-seq[@litchar{(} @nonterm{expr} @kleenestar{@nonterm{expr}} @litchar{)}]) @(define if-expr-stx @BNF-seq[@litchar{(} @litchar{if} @nonterm{expr} @nonterm{expr} @nonterm{expr} @litchar{)}]) @(define lambda-expr-stx @BNF-seq[@litchar{(} @litchar{lambda} @litchar{(} @kleenestar{@nonterm{id}} @litchar{)} @kleeneplus{@nonterm{expr}} @litchar{)}]) @(define lambda2-expr-stx @BNF-seq[@litchar{(} @litchar{lambda} @litchar{(} @kleenestar{@nonterm{id}} @litchar{)} @kleenestar{@nonterm{definition}} @kleeneplus{@nonterm{expr}} @litchar{)}]) @(define and-expr-stx @BNF-seq[@litchar{(} @litchar{and} @kleenestar{@nonterm{expr}} @litchar{)}]) @(define or-expr-stx @BNF-seq[@litchar{(} @litchar{or} @kleenestar{@nonterm{expr}} @litchar{)}]) @(define cond-expr-stx @BNF-seq[@litchar{(} @litchar{cond} @kleenestar{@BNF-group[@litchar{[} @nonterm{expr} @kleenestar{@nonterm{expr}} @litchar{]}]} @litchar{)}]) @(define (make-let-expr-stx kw) @BNF-seq[@litchar{(} kw @litchar{(} @kleenestar{@BNF-group[@litchar{[} @nonterm{id} @nonterm{expr} @litchar{]}]} @litchar{)} @kleeneplus{@nonterm{expr}} @litchar{)}]) @(define let-expr-stx (make-let-expr-stx @litchar{let})) @(define let*-expr-stx (make-let-expr-stx @litchar{let*})) @;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @section{Definitions} A definition of the form @moreguide["define"]{definitions} @schemeblock[#, @val-defn-stx] binds @nonterm{id} to the result of @nonterm{expr}, while @schemeblock[#, @fun-defn-stx] binds the first @nonterm{id} to a function (also called a @defterm{procedure}) that takes arguments as named by the remaining @nonterm{id}s. In the function case, the @nonterm{expr}s are the body of the function. When the function is called, it returns the result of the last @nonterm{expr}. @defexamples[ #:eval ex-eval (code:line (define five 5) (code:comment #, @t{defines @scheme[five] to be @scheme[5]})) (code:line (define (piece str) (code:comment #, @t{defines @scheme[piece] as a function}) (substring str 0 five)) (code:comment #, @t{of one argument})) five (piece "hello world") ] Under the hood, a function definition is really the same as a non-function definition, and a function name does not have to be used in a function call. A function is just another kind of value, though the printed form is necessarily less complete than the printed form of a number or string. @examples[ #:eval ex-eval piece substring ] @; FIXME: check that everything says "procedure" and not "primitive" A function definition can include multiple expressions for the function's body. In that case, only the value of the last expression is returned when the function is called. The other expressions are evaluated only for some side-effect, such as printing. @defexamples[ #:eval ex-eval (define (greet name) (printf "returning a greeting for ~a...\n" name) (string-append "hello " name)) (greet "universe") ] Scheme programmers prefer to avoid assignment statements. It's important, though, to understand that multiple expressions are allowed in a definition body, because it explains why the following @scheme[nogreet] function simply returns its argument: @def+int[ #:eval ex-eval (define (nogreet name) string-append "hello " name) (nogreet "world") ] Within @scheme[nogreet], there are no parentheses around @scheme[string-append "hello " name], so they are three separate expressions instead of one function-call expression. The expressions @scheme[string-append] and @scheme["hello "] are evaluated, but the results are never used. Instead, the result of the function is just the result of the expression @scheme[name]. @; ---------------------------------------------------------------------- @section[#:tag "indentation"]{An Aside on Indenting Code} Line breaks and indentation are not significant for parsing Scheme programs, but most Scheme programmers use a standard set of conventions to make code more readable. For example, the body of a definition is typically indented under the first line of the definition. Identifiers are written immediately after an open parenthesis with no extra space, and closing parentheses never go on their own line. DrScheme automatically indents according to the standard style when you type Enter in a program or @tech{REPL} expression. For example, if you hit Enter after typing @litchar{(define (greet name)}, then DrScheme automatically inserts two spaces for the next line. If you change a region of code, you can select it in DrScheme and hit Tab, and DrScheme will re-indent the code (without inserting any line breaks). Editors like Emacs offer a Scheme mode with similar indentation support. Re-indenting not only makes the code easier to read, it gives you extra feedback that your parentheses are matched in the way that you intended. For example, if you leave out a closing parenthesis after the last argument to a function, automatic indentation starts the next line under the first argument, instead of under the @scheme[define] keyword: @schemeblock[ (define (nogreet name (string-append "hello " name))) ] Furthermore, when an open parenthesis has no matching close parenthesis in a program, both @exec{mzscheme} and DrScheme use the source's indentation to suggest where it might be missing. @;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @section{Identifiers} Scheme's syntax for identifiers is especially liberal. Excluding the special characters @moreguide["binding"]{identifiers} @t{ @hspace[2] @litchar{(} @litchar{)} @litchar{[} @litchar{]} @litchar["{"] @litchar["}"] @litchar{"} @litchar{,} @litchar{'} @litchar{`} @litchar{;} @litchar{#} @litchar{|} @litchar{\} } and except for the sequences of characters that make number constants, almost any sequence of non-whitespace characters forms an @nonterm{id}. For example @schemeid[substring] is an identifier. Also, @schemeid[string-append] and @schemeid[a+b] are identifiers, as opposed to arithmetic expressions. Here are several more examples: @schemeblock[ #, @schemeid[+] #, @schemeid[Apple] #, @schemeid[integer?] #, @schemeid[call/cc] #, @schemeid[call-with-composable-continuation] #, @schemeid[x-1+3i] ] @;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @section{Function Calls@aux-elem{ (Procedure Applications)}} We have already seen many function calls, which are called @defterm{procedure applications} in more traditional Scheme terminology. The syntax of a function call is @moreguide["application"]{function calls} @schemeblock[ #, app-expr-stx ] where the number of @nonterm{expr}s determines the number of arguments supplied to the function named by @nonterm{id}. The @schememodname[scheme] language pre-defines many function identifiers, such as @scheme[substring] and @scheme[string-append]. More examples are below. In example Scheme code throughout the documentation, uses of pre-defined names are hyperlinked to the reference manual. So, you can click on an identifier to get full details about its use. @interaction[ (code:line (string-append "hello" " " "scheme") (code:comment #, @t{append strings})) (code:line (substring "hello scheme" 6 12) (code:comment #, @t{extract a substring})) (code:line (string-length "scheme") (code:comment #, @t{get a string's length})) (code:line (string? "hello scheme") (code:comment #, @t{recognize strings})) (string? 1) (code:line (sqrt 16) (code:comment #, @t{find a square root})) (sqrt -16) (code:line (+ 1 2) (code:comment #, @t{add numbers})) (code:line (- 2 1) (code:comment #, @t{subtract numbers})) (code:line (< 2 1) (code:comment #, @t{compare numbers})) (>= 2 1) (code:line (number? "hello scheme") (code:comment #, @t{recognize numbers})) (number? 1) (code:line (equal? 1 "hello") (code:comment #, @t{compare anything})) (equal? 1 1) ] @;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @section{Conditionals with @scheme[if], @scheme[and], @scheme[or], and @scheme[cond]} The next simplest kind of expression is an @scheme[if] conditional: @schemeblock[ #, if-expr-stx ] @moreguide["conditionals"]{conditionals} The first @nonterm{expr} is always evaluated. If it produces a non-@scheme[#f] value, then the second @nonterm{expr} is evaluated for the result of the whole @scheme[if] expression, otherwise the third @nonterm{expr} is evaluated for the result. @examples[ (if (> 2 3) "bigger" "smaller") ] @def+int[ (define (reply s) (if (equal? "hello" (substring s 0 5)) "hi!" "huh?")) (reply "hello scheme") (reply "\u03BBx:(\u03BC\u03B1.\u03B1\u2192\u03B1).xx") ] Complex conditionals can be formed by nesting @scheme[if] expressions. For example, you could make the @scheme[reply] function work when given non-strings: @schemeblock[ (define (reply s) (if (string? s) (if (equal? "hello" (substring s 0 5) "hi!" "huh?")) "huh?")) ] Instead of duplicating the @scheme["huh?"] case, this function is better written as @schemeblock[ (define (reply s) (if (if (string? s) (equal? "hello" (substring s 0 5) #f)) "hi!" "huh?")) ] but these kinds of nested @scheme[if]s are difficult to read. Scheme provides more readable shortcuts through the @scheme[and] and @scheme[or] forms, which work with any number of expressions: @moreguide["and+or"]{@scheme[and] and @scheme[or]} @schemeblock[ #, and-expr-stx #, or-expr-stx ] The @scheme[and] form short-circuits: it stops and returns @scheme[#f] when an expression produces @scheme[#f], otherwise it keeps going. The @scheme[or] form similarly short-circuits when it encounters a true result. @defexamples[ (define (reply s) (if (and (string? s) (>= (string-length s) 5) (equal? "hello" (substring s 0 5))) "hi!" "huh?")) (reply "hello scheme") (reply 17) ] Another common pattern of nested @scheme[if]s involves a sequence of tests, each with its own result: @schemeblock[ (define (reply-more s) (if (equal? "hello" (substring s 0 5)) "hi!" (if (equal? "goodbye" (substring s 0 7)) "bye!" (if (equal? "?" (substring s (- (string-length s) 1))) "I don't know" "huh?")))) ] The shorthand for a sequence of tests is the @scheme[cond] form: @moreguide["cond"]{@scheme[cond]} @schemeblock[ #, cond-expr-stx ] A @scheme[cond] form contains a sequence of clauses between square brackets. In each clause, the first @nonterm{expr} is a test expression. If it produces true, then the clause's remaining @nonterm{expr}s are evaluated, and the last one in the clause provides the answer for the entire @scheme[cond] expression; the rest of the clauses are ignored. If the test @nonterm{expr} produces @scheme[#f], then the clause's remaining @nonterm{expr}s are ignored, and evaluation continues with the next clause. The last clause can use @scheme[else] as a synonym for a @scheme[#t] test expression. Using @scheme[cond], the @scheme[reply-more] function can be more clearly written as follows: @def+int[ (define (reply-more s) (cond [(equal? "hello" (substring s 0 5)) "hi!"] [(equal? "goodbye" (substring s 0 7)) "bye!"] [(equal? "?" (substring s (- (string-length s) 1))) "I don't know"] [else "huh?"])) (reply-more "hello scheme") (reply-more "goodbye cruel world") (reply-more "what is your favorite color?") (reply-more "mine is lime green") ] The use of square brackets for @scheme[cond] clauses is a convention. In Scheme, parentheses and square brackets are actually interchangable, as long as @litchar{(} is matched with @litchar{)} and @litchar{[} is matched with @litchar{]}. Using square brackets in a few key places makes Scheme code even more readable. @;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @section{Function Calls, Again} In our earlier grammar of function calls, we oversimplified. The actual syntax of a function call allows an arbitrary expression for the function, instead of just an @nonterm{id}: @moreguide["application"]{function calls} @schemeblock[ #, app2-expr-stx ] The first @nonterm{expr} is often an @nonterm{id}, such as @scheme[string-append] or @scheme[+], but it can be anything that evaluates to a function. For example, it can be a conditional expression: @def+int[ (define (double v) ((if (string? v) string-append +) v v)) (double "hello") (double 5) ] Syntactically, the first expression in a function call could even be a number---but that leads to an error, since a number is not a function. @interaction[(1 2 3 4)] When you accidentally omit a function name or when you use parentheses around an expression, you'll most often get an ``expected a procedure'' error like this one. @;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @section{Anonymous Functions with @scheme[lambda]} Programming in Scheme would be tedious if you had to name all of your numbers. Instead of writing @scheme[(+ 1 2)], you'd have to write @moreguide["lambda"]{@scheme[lambda]} @interaction[ (define a 1) (define b 2) (+ a b) ] It turns out that having to name all your functions can be tedious, too. For example, you might have a function @scheme[twice] that takes a function and an argument. Using @scheme[twice] is convenient if you already have a name for the function, such as @scheme[sqrt]: @def+int[ #:eval ex-eval (define (twice f v) (f (f v))) (twice sqrt 16) ] If you want to call a function that is not yet defined, you could define it, and then pass it to @scheme[twice]: @def+int[ #:eval ex-eval (define (louder s) (string-append s "!")) (twice louder "hello") ] But if the call to @scheme[twice] is the only place where @scheme[louder] is used, it's a shame to have to write a whole definition. In Scheme, you can use a @scheme[lambda] expression to produce a function directly. The @scheme[lambda] form is followed by identifiers for the function's arguments, and then the function's body expressions: @schemeblock[ #, lambda-expr-stx ] Evaluating a @scheme[lambda] form by itself produces a function: @interaction[(lambda (s) (string-append s "!"))] Using @scheme[lambda], the above call to @scheme[twice] can be re-written as @interaction[ #:eval ex-eval (twice (lambda (s) (string-append s "!")) "hello") (twice (lambda (s) (string-append s "?!")) "hello") ] Another use of @scheme[lambda] is as a result for a function that generates functions: @def+int[ #:eval ex-eval (define (make-add-suffix s2) (lambda (s) (string-append s s2))) (twice (make-add-suffix "!") "hello") (twice (make-add-suffix "?!") "hello") (twice (make-add-suffix "...") "hello") ] Scheme is a @defterm{lexically scoped} language, which means that @scheme[s2] in the function returned by @scheme[make-add-suffix] always refers to the argument for the call that created the function. In other words, the @scheme[lambda]-generated function ``remembers'' the right @scheme[s2]: @interaction[ #:eval ex-eval (define louder (make-add-suffix "!")) (define less-sure (make-add-suffix "?")) (twice less-sure "really") (twice louder "really") ] We have so far referred to definitions of the form @scheme[(define #, @nonterm{id} #, @nonterm{expr})] as ``non-function definitions.'' This characterization is misleading, because the @nonterm{expr} could be a @scheme[lambda] form, in which case the definition is equivalent to using the ``function'' definition form. For example, the following two definitions of @scheme[louder] are equivalent: @defs+int[ #:eval ex-eval [(define (louder s) (string-append s "!")) code:blank (define louder (lambda (s) (string-append s "!")))] louder ] Note that the expression for @scheme[louder] in the second case is an ``anonymous'' function written with @scheme[lambda], but, if possible, the compiler infers a name, anyway, to make printing and error reporting as informative as possible. @;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @section[#:tag "local-binding-intro"]{Local Binding with @scheme[define], @scheme[let], and @scheme[let*]} It's time to retract another simplification in our grammar of Scheme. In the body of a function, definitions can appear before the body expressions: @moreguide["intdefs"]{local (internal) definitions} @schemeblock[ #, fun-defn2-stx #, lambda2-expr-stx ] Definitions at the start of a function body are local to the function body. @defexamples[ (define (converse s) (define (starts? s2) (code:comment #, @t{local to @scheme[converse]}) (define len2 (string-length s2)) (code:comment #, @t{local to @scheme[starts?]}) (and (>= (string-length s) len2) (equal? s2 (substring s 0 len2)))) (cond [(starts? "hello") "hi!"] [(starts? "goodbye") "bye!"] [else "huh?"])) (converse "hello!") (converse "urp") (eval:alts (code:line starts? (code:comment #, @t{outside of @scheme[converse], so...})) (parameterize ([current-namespace (make-base-namespace)]) (eval 'starts?))) ] Another way to create local bindings is the @scheme[let] form. An advantage of @scheme[let] is that it can be used in any expression position. Also, @scheme[let] binds many identifiers at once, instead of requiring a separate @scheme[define] for each identifier. @moreguide["intdefs"]{@scheme[let] and @scheme[let*]} @schemeblock[ #, let-expr-stx ] Each binding clause is an @nonterm{id} and a @nonterm{expr} surrounded by square brackets, and the expressions after the clauses are the body of the @scheme[let]. In each clause, the @nonterm{id} is bound to the result of the @nonterm{expr} for use in the body. @interaction[ (let ([x 1] [y 2]) (format "adding ~s and ~s produces ~s" x y (+ x y))) ] The bindings of a @scheme[let] form are available only in the body of the @scheme[let], so the binding clauses cannot refer to each other. The @scheme[let*] form, in contrast, allows later clauses to use earlier bindings: @interaction[ (let* ([x 1] [y 2] [z (+ x y)]) (format "adding ~s and ~s produces ~s" x y z)) ] @; ---------------------------------------------------------------------- @close-eval[ex-eval]