#lang shplait // The first line above is important. It tells DrRacket which // variant of Racket you want to use. // This is a comment that continues to the end of the line. /* This is a block comment, which starts with "/*" and ends with a "*/". Block comments can be nested, which is why I can name the start and end sequences in this comment. */ // #// comments out a single form. We use #// below // to comment out error examples. #// (1+1) / 0 /////////////////////////////////////////// // Built-in atomic data // Booleans #true // true #false // false // Integers 1 -42 1_000_000 // Strings "apple" "banana cream pie" // Symbols #'apple #'banana_cream_pie // Characters (unlikely to be useful) #{#\a} #{#\b} #{#\A} #{#\space} // same as #{#\ } (with a space after \) /////////////////////////////////////////// // Built-in operators and functions on atomic data ! #true // => #false ! #false // => #true 1 + 2 // => 3 1 - 2 // => -1 1 * 2 // => 2 // etc. 1 < 2 // => #true 1 > 2 // => #false 1 == 2 // => #false 1 <= 2 // => #true 1 >= 2 // => #false string_append("a", "b") // => "ab" string_get("a", 0) // => #{#\a} "a" == "b" // => #false "a" +& "b" // => "ab" "a" +& 1 // => "a1" 1 +& "a" // => "1a" #{#\a} == #{#\b} // => #false // Beware that `===` is object identity (or "pointer equality"), // not the usual `==` equality "apple" == ("a" +& "pple") // => #true "apple" === ("a" +& "pple") // => #false /////////////////////////////////////////// // Types 1 // => 1, but says "Int" first when in interactions 1 + 4/2 // => 3, but says "Int" first when in interactions "a" // => "a", but says "String" string_length // => #, but says "String -> Int" #// 1 + "2" // error: Int vs String /////////////////////////////////////////// // Syntax objects '1 + 2' // => '1 + 2', but says "Syntax" first '1' // => '1' '"a"' // => '"a"' 'a' // => 'a', and says "Syntax" first #'a // => #'a, and says "Symbol" first syntax_to_symbol('a') // => #'a syntax_to_integer('1') // => 1 integer_to_syntax(1) // => '1' #// syntax_to_string('1') // says "String", but then reports an error /////////////////////////////////////////// // Basic expression forms // Prefix and infix operators // // ! #false // => #true 1 + 2 // => 3 // Spaces are not required !#false // => #true 1+2 // => 3 // Function calls // (, ...) string_append("a", "b") // => "ab" // Conditionals // // cond // | : // | ... // // or // // cond // | : // | ... // | ~else: cond | 2 < 1: 17 | 2 > 1: 18 // => 18 cond // second expression not evaluated | #true: 8 | #false: 1/0 // => 8 cond // in fact, second test not evaluated | #true: 9 | 1/0 == 0: 0 // => 9 cond // any number of cond-lines allowed | 3 < 1: 0 | 3 < 2: 1 | 3 < 3: 2 | 3 < 4: 3 // => 3 cond // ~else allowed as last case | #'a == #'b: 0 | #'a == #'c: 1 | ~else: 2 // => 2 // After `:`, you can optionally start on a new line with indentation cond | 2 < 1: 17 | 2 > 1: 18 | ~else: 19 // => 18 // You don't have to start `|` on a new line cond | 2 < 1: 17 | 2 > 1: 18 | ~else: 19 // => 18 // A `|` on a new line either lines up with the start of // the form, as in most example above, or the first `|` of // the form cond | 2 < 1: 17 | 2 > 1: 18 | ~else: 19 // => 18 // If, a simpler form for single test // // if | | if 3 < 1 | "apple" | "banana" // => "banana" // And and Or, short-circuiting // && // || #true && #true // => #true #true && #false // => #false 2 < 1 && #true // => #false 2 < 1 && (1/0 == 0) // => #false (second expression is not evaluated) #false || #true // => #true #false || #false // => #false 1 < 2 || (1/0 == 0) // => #true (second expression is not evaluated) #true && #true && #true // => #true #false || #false || #false // => #false /////////////////////////////////////////// // Built-in compound data // Lists [] // => [] [1, 2, 3] // => [1, 2, 3], but shows "Listof(Int)" first [1, 2, 3+4] // => [1, 2, 7], but shows "Listof(Int)" first cons(0, [1, 2, 3]) // => [0, 1, 2, 3] #// [1, "a"] // error: Int vs String ["a", "b"] // ["a", "b"] [[1, 2], [3]] // [[1, 2], [3]], but shows "Listof(Listof(Int))" first cons(1, []) // => [1] cons(#'a, cons(#'b, [])) // => [#'a, #'b] [[1], []] // => [[1], []] cons([1], []) // => [[1]] append([1, 2], []) // => [1, 2] append([1, 2], [3, 4]) // => [1, 2, 3, 4] first([1, 2, 3]) // => 1 rest([1, 2, 3]) // => [2, 3] first(rest([1, 2, 3])) // => 2 list_get([1, 2, 3], 2) // => 3 '[1, 2, 3]' // => '[1, 2, 3]', but says "Syntax" first '[1, 2, 3+4]' // => '[1, 2, 3 + 4]', but says "Syntax" first first(syntax_to_list('[1, 2, 3]')) // => '1', but says "Syntax" first // Arrays Array(1, 2, 3, 4) // => Array(1, 2, 3, 4), but shows "Arrayof(Int)" first (Array(1, 2, 3, 4))[0] // => 1 // Boxes box(1) // => box(1) unbox(box(1)) // => 1 // Tuples values(1, 2) // => values(1, 2), but says "Int * Int" first values(1, "a") // => values(1, "a"), but says "Int * String" first /////////////////////////////////////////// // Definitions // Defining constants // // def = // // or // // def : def PI = 3 // we have only integers; close enough PI * 10 // => 30 def TAU: PI * 2 TAU // 6 // Defining functions // // fun (, ...): fun circle_area(r): PI * r * r circle_area(10) // => 300 fun num_is_odd(x): if x == 0 | #false | num_is_even(x-1) fun num_is_even(x): if x == 0 | #true | num_is_odd(x-1) num_is_odd(12) // => #false // Definition matching a tuple // // def values(, ...) = // // or with `:` instead of `=` def values(size, color) = values(10, #'red) size // => 10 color // => #'red // Declaring types // // def :: = def e :: Int = 2 // Declaring argument and result types // // fun ( :: , ...) :: : fun circle_perimeter(r :: Int) :: Int: 2 * PI * r circle_perimeter(10) // => 62.8318 // Defining datatypes // // type // | ( :: , ...) // | ... type Animal | snake(name :: Symbol, weight :: Int, food :: Symbol) | tiger(name :: Symbol, stripe_count :: Int) snake(#'Slimey, 10, #'rats) // => snake(#'Slimey, 10, #'rats) tiger(#'Tony, 12) // => tiger(#'Tony, 12) [tiger(#'Tony, 12)] // => [tiger(#'Tony, 12)] #// snake(10, #'Slimey, 5) // error: Symbol vs Int snake.name(snake(#'Slimey, 10, #'rats)) // => #'Slimey tiger.name(tiger(#'Tony, 12)) // => #'Tony snake(#'Slimey, 10, #'rats) is_a snake // => #true snake(#'Slimey, 10, #'rats) is_a tiger // => #false #// 5 is_a snake // error: Int vs Animal // A type can have any number of variants: type Shape | square(side :: Int) | circle(radius :: Int) | triangle(height :: Int, width :: Int) fun is_curvy(s :: Shape) :: Boolean: s is_a circle is_curvy(square(5)) // => #false is_curvy(circle(5)) // => #true is_curvy(triangle(3, 5)) // => #false // Parameterized datatypes (like "generics" in Java) // // type (?, ...) // | ( :: , ...) // | ... type Treeof(?a) | leaf(val :: ?a) | node(left :: Treeof(?a), right :: Treeof(?a)) leaf(10) // => leaf(10), but says "Treeof(Int)" first leaf(circle(8)) // => leaf(circle(8)), but says "Treeof(Shape)" first /////////////////////////////////////////// // Local binding forms // Blocks // // block: // // ... // block: def x = 10 x + x // => 20 #// x // error: unbound identifier fun to_the_fourth(x): block: fun squared(n): n * n squared(squared(x)) to_the_fourth(10) // => 10000 fun also_to_the_fourth(x): // function body has an implicit `block` // (as does `if`, `cond`, and more) fun squared(n): n * n squared(squared(x)) also_to_the_fourth(10) // => 10000 to_the_fourth(10) // => 10000 block: fun num_is_odd(x): if x == 0 | #false | num_is_even(x-1) fun num_is_even(x): if x == 0 | #true | num_is_odd(x-1) num_is_odd(12) // => #false // Let (more traditional but less regular) // // let = : let x = 1: let y = 2: x+y // => 3 let x = 1: let x = 2: x // => 2 /////////////////////////////////////////// // Datatype case dispatch // Variant dispatch via `match` // // match // | (, ...): // | ... // // or // // match // | (, ...): // | ... // | ~else: match snake(#'Slimey, 10, #'rats) | snake(n, w, f): n | tiger(n, sc): n // => 'Slimey fun animal_name(a): match a | snake(n, w, f): n | tiger(n, sc): n animal_name(snake(#'Slimey, 10, #'rats)) // => #'Slimey animal_name(tiger(#'Tony, 12)) // => #'Tony #// animal_name(10) // error: Int vs Animal #// fun animal_weight(a): match a | snake(n, w, f): w // error: missing tiger case fun animal_weight(a): match a | snake(n, w, f): w | ~else: 0 // `match` also works on lists with special `[]` and `cons` // patterns to match empty and non-empty lists, respectively fun list_length(lst): match lst | []: 0 | cons(f, rst): 1 + list_length(rst) list_length(["apple", "banana", "coconut"]) // => 3 /////////////////////////////////////////// // First-class functions // Anonymous function: // // fun (, ...): // // That is, like a function definition, but without the // function name fun (x): x+1 // => #, but shows "Int -> Int" first (fun (x): x+1)(10) // => 11 def add_one: fun (x): x+1 add_one(10) // => 11 def add_two :: Int -> Int: fun (x): x+2 add_two(10) // => 12 fun make_adder(n): fun (m): m+n make_adder(8) // => # def add_five = make_adder(5) add_five(12) // => 17 make_adder(5)(12) // => 17 map(fun (x): x*x, [1, 2, 3]) // => [1, 4, 9] /////////////////////////////////////////// // Side-effects // IMPORTANT: in this class using a side-effect is // usually wrong; avoid all side-effects // Defining and assigning mutable variables // // def mutable = // // := def mutable count = 0 count := count + 1 count // => 1 block: count := count + 1 count // => 2 block: // note: demonstrates local assignment, def mutable x = [] // but it's terrible style x := cons(1, x) x := cons(2, x) x // => [2, 1] // set_box is a function: def B = box(10) set_box(B, 12) B // => box(12) unbox(B) // => 12 /////////////////////////////////////////// // Polymorphic functions def identity = fun(x): x identity // => #, but shows "?a -> ?a" first identity("a") // => "a" identity(1) // => 1 [] // => [], but shows "Listof(?_a)" first cons // => #, but shows "(?a, Listof(?a)) -> Listof(?a)" first def b = box([]) b // => box([]), but shows "Boxof(Listof(?_a))" first set_box(b, [1]) b // => box([]), but shows "Boxof(Listof(Int))" first #// set_box(b, ["a"]) // error: Int vs String /////////////////////////////////////////// // Testing // A test case is written with `check` // // check: // // ~is // // or // // check: // // ~raises check: 5 + 5 ~is 10 // no output check: 5 + 4 ~is 10 // prints a test-failure message check: cond | #false: "no" ~raises "no matching" check: cond | #false: "no" ~raises "bad dog" // prints a test-failure message check: cond | #true: "yes" ~raises "no matching" // prints a test-failure message "Note: those test-failure error messages are supposed to show up!" /////////////////////////////////////////// // More information // Use the "Help Desk" item in DrRacket's "Help" menu // and see "Shplait Language"