- 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
hash
es
- 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