• Major themes:
    • Types
      • Static/dynamic typing:
        • Static typechecking analyses the program (without running any of it!) to prevent some class of errors
        • Dynamic typechecking just makes sure at runtime -- more permissive, but easier to write bugs that you don't catch until runtime
        • Contrast:
          • SML (static): fun f x = y doesn't even allow you to define the function
          • Racket (dynamic): (define (f x) y) doesn't complain; error only discovered when f is called
      • Polymorphism
        • Abstracting data types or functions to work on many different kinds of input
          • e.g. in SML, hd : 'a list -> a is polymorphic
        • Very important in statically typed languages!
        • Not as important to think about in dynamic languages like Racket or Ruby, since you don't have to prove to the typechecker that your program's okay -- as long as you don't try to use an object in some nonsensical way (e.g. (car 4)), you're fine.
      • Soundness/completeness
        • A type system is sound (with respect to some guarantee) if it never allows a program that violates that guarantee.
          • e.g. SML's type system is sound with respect to guaranteeing no undefined variable lookups
          • ...but not sound with respect to guaranteeing array-out-of-bounds errors.
        • ...and complete if it never rejects a program that fulfills the guarantee.
          • e.g. Ruby's type system is complete with respect to guaranteeing no undefined variable lookups: if you reference an undefined variable, Ruby will catch it (albeit at runtime).
    • Mutability
      • In SML, immutability is the default -- only exception is ref/!/:=
        • The only sources of mutability! So simple!
      • In Racket, again, most things are immutable -- only exceptions are set!/mcons/set-mcar!/set-mcdr!
        • ...okay, and some other functions for specific data structures like hashes
      • In Ruby, basically everything is mutable
        • Technically you can only mutate an object by calling the methods it defines; if you define a data structure without any setters, it's kinda immutable
        • ...but in practice, basically everything has tons of methods for mutation: arrays, hashes, even strings.
        • ...and there's always a workaround: somebody can re-open a class to add methods that mutate it. Bad style, but possible.
    • Syntactic sugar: language features that don't make the language any more powerful, they're just shorthand that makes it easier to program in.
      • In SML:
        • Every infix operator is sugar: 1 + 2 is shorthand for (op+) (1, 2), etc.
        • List notation ([1, 2, 3]) is sugar for (op::) (1, (op::) (2, (op::) (3, [])))
        • Even tuples are just sugar for records: ("a", 4) means {1="a", 2=4}
      • In Racket, macros are the source of all sugar
        • Some built-in ones, e.g. set!
        • You can also define your own!
          • e.g. (define-syntax-rule (my-or a b) (if a b #f))
          • This tells Racket, "whenever you see (my-or e1 e2), replace it with (if e1 e2 #f)
          • or more powerful (letting you define keywords then and else), (define-syntax my-if (syntax-rules (then else) [(my-if a then b else c) (if a b c)]))
          • See what Racket is thinking with (syntax->datum (expand-once '(my-or foo bar)))
      • In Ruby, infix operators and setters are sugar (a + b means "call a.+ on b"; obj.x = :foo means "call obj.x= on :foo"
    • Higher-order functions
      • Functions are values!
      • The big three: map, filter, fold
  • Not "themes," but important:
    • Structures and signatures in SML
      • Structure ~= namespace
      • Signature ~= interface (but more restrictive!)
    • Pattern matching
      • Mini-language for specifying "shapes" of objects and extracting pieces of them
    • Tail calls:
      • "tail position": if it's the last thing you do...
    • Subtyping
      • Width subtyping: having more fields than you need is okay
      • Depth subtyping: fields with more specific types are okay
        • Violates soundness when combined with mutability!
      • Function subtyping:
        • Contravariant arguments: passing a more specific argument is okay
        • Covariant returns: treating the result as a less specific type is okay