I briefly discussed some of the applications people have made of Scheme and its predecessor, Lisp. Richard Stallman wrote the first version of emacs in Lisp and much of emacs is still written in Lisp. Stallman was the founder of the GNU project and an early pioneer of the free software movement. For example, here are some entries from my emacs initialization file (called .emacs):
;; set some defaults for text mode (setq default-major-mode 'text-mode) (setq initial-major-mode 'text-mode) (setq default-fill-column 79) ;; inhibit startup message (setq inhibit-startup-message 1) (setq load-path (cons "/Users/stuartreges/" load-path))The setq function in Lisp is like the define procedure in Scheme.
Then we spent some time talking about the idea of macros. In an editor like emacs, you can define a macro that performs a sequence of editing commands. I personally use emacs macros all the time to get my work done. The commands for defining macros in emacs are fairly simple:
Emacs Command | Description |
---|---|
CTRL-X ( | begin defining a macro |
CTRL-X ) | end macro definition |
CTRL-X e | execute the macro |
It can be tricky to get macros right. For example, if you were going to change all occurrences of "true" to "1", you wouldn't want to change "construed" to "cons1d" and you wouldn't want to replace the word if it appeared in a quoted string or a comment. Any reasonable macro mechanism would be careful to avoid such obvious mistakes.
But there are lots of other tricky cases. Consider, for example, this C macro:
#define SUM(x, y) x*x + y*yThe idea is that you might want to often find the sum of squares of two values and rather than use a function, you might want to have the expansion happen in-line in the code itself. I asked people where this might cause a problem and someone pointed out that you might pass a sum like "x + 1":
z = SUM(x + 1, y);This would be expaned by the macro into:
z = x + 1*x + 1 + y*y;That's not what you would expect. How do we fix it? By adding parentheses to the macro:
#define SUM(x, y) (x)*(x) + (y)*(y)Now our code expands to:
z = (x + 1)*(x + 1) + (y)*(y);When you write macros in C and C++, you often have to include a lot of parentheses. We saw that even this level of parentheses is not enough. For example, this expression:
z = c * SUM(a, b);is expanded into:
z = c * (a)*(a) + (b)*(b);So the macro should really be:
#define SUM(x, y) ((x)*(x) + (y)*(y))Even this isn't enough in some cases where someone might pass a value like x++. The situation gets even worse if you try to introduce a variable declaration because you might declare a variable like "foo" that shadows another variable called "foo". So people tend to declare very bizarre variable names and hope that there is no conflict.
The point is that macros can be tricky and the macro facility in C and C++ is very weak. Scheme, on the other hand, has a very powerful macro facility that doesn't require you to worry about adding extra parentheses or using weird variable names. The Scheme mechanism allows you to declare what programming language research refer to as hygienic macros.
In the last lecture we were working with this function that has a side effect that allows us to see when it is called:
(define (work x) (display "work called: ") (display x) (newline) (+ x 1))In the Pretty Big variant of Scheme, you define a macro using a special form called define-syntax. It's general form is:
(define-syntax if2 (syntax-rules () ((if2 test e1 e2) (cond (test e1) (else e2)))))In this form we don't introduce any keywords. We indicate that the pattern is the word if2 followed by three parameters. The cond indicates what value to return. We found that when you define this with a macro, Scheme delays the evaluation of the arguments. So we found that this macro had similar properties to if:
> (if2 (< 2 3) (work 3) (work 4)) work called: 3 4 > (if2 (> 2 3) (work 3) (work 4)) work called: 4 5Often you want to include special keywords for your construct. For example, suppose that we like the ML-style if/else construct with the keywords "then" and "else". We can write a macro to convert from that form to the built-in if. I called it "if2":
(define-syntax if3 (syntax-rules (then else) ((if3 e1 then e2 else e3) (if e1 e2 e3))))This definition indicates that the new form will be called if3, meaning that we'll call it like this:
(if3 (< 3 4) then "foo" else "bar")We saw that Scheme enforces this syntax, rejecting anything that doesn't fit the pattern that we provided in the call on define-syntax.
After the pattern in the define-syntax there is an expression indicating how to evaluate this. It translates a call like the one above into a call on the built-in if:
(if e1 e2 e3)So if you don't like Scheme's built-in syntax, you can define your own, including your own keywords.
Even in Scheme, writing macros can be tricky. We looked at how to write a macro that would be equivalent in behavior to the built-in or. I asked people what or returns and some people seemed to think that it always returns either #t or #f. That's not quite right. The built-in or evaluates its arguments as long as they evaluate to #f or until one of them evaluates to something other than #f. If that happens, it returns that value. This is part of the general Scheme notion that #f represents false and everything else represents true. For example:
> (or 2 3) 2 > (or (> 2 3) (+ 2 3)) 5Only if all arguments return #f does or return #f:
> (or (> 2 3) (= 2 3)) #fWe can simulate this behavior with an if:
(define-syntax or2 (syntax-rules () ((or2 a b) (if a a b))))This provides a pretty good definition for or. It gets the same answer for all of the cases above:
> (or2 2 3) 2 > (or2 (> 2 3) (+ 2 3)) 5 > (or2 (> 2 3) (= 2 3)) #fThere is a subtle problem with this, though. Someone said that it potentially evaluates its first argument twice. This can be a problem if the expression being evaluated has a side-effect like a call on set! or a call on display:
> (or2 (begin (display "ha") (< 2 3)) (= 2 3)) haha#tWe get two occurrences of "ha" because the expression is evaluated twice. The built-in or has no such problem:
> (or (begin (display "ha") (< 2 3)) (= 2 3)) ha#tWe were able to fix this by introducing a local variable with a let:
(define-syntax or3 (syntax-rules () ((or3 a b) (let ((aval a)) (if aval aval b)))))This version behaves correctly:
> (or3 (begin (display "ha") (< 2 3)) (= 2 3)) ha#tI showed some quotes about LISP/Scheme from Paul Graham. He has built a successful software career out of Lisp programming. He wrote the initial version of Yahoo! Store in Lisp and later sold the company to Yahoo.
My personal favorite quote is from Eric Raymond's essay "How to Become a Hacker":
Lisp is worth learning for the profound enlightenment experience you will have when you finally get it; that experience will make you a better programmer for the rest of your days, even if you never actually use Lisp itself a lot.We started our exploration of Ruby. I mentioned that the 341 class usedn to cover Smalltalk as its final programming language. I know quite a bit about the language, but I thought it would be more fun to look at Ruby. Like Smalltalk, it is a pure object oriented language. And like Smalltalk, it is a dynamically typed language. This will allow us to complete the language survey I mentioned in handout #1:
|
Object-oriented |
Functional |
Statically typed |
Java/C++ |
ML |
Dynamically typed |
Ruby |
Scheme |
The most recent wave of innovation came in the early 1990's. So what happened in the early 1990's? That's when Tim Berners Lee invented the world wide web and that changed everything. The big problem with the web was that it had only one-way communication. You could create web pages that would present data along with hyperlinks, but the page couldn't interact with the user.
Sun was working on Java at the time, although they were targeting it for embedded systems (programs running on handheld devices like cell phones). When the web became popular, they realized that Java would be useful for programming applets that would run in a browser. Applets have always had some degree of popularity, but they have generally turned out to be too big of a pain for people.
The other dominant paradigm that emerged was the idea of running software on a server that would interact with a user. So the challenge was to program the server. Suddenly scripting languages became very important because they allowed programmers to quickly develop code to be run on the server. This was a very different kind of programming task than writing a gigantic program like Microsoft Word. Plus, because it was running on the server, you had a great deal of flexibility about what language to use (any language that you could put on your server).
Perl was the early favorite scripting language, but many other popular languages have emerged to fill this niche. Python and Ruby are two such languages. I gave the analogy that they are to the programming world what indie films are to the movie industry. Java, C++ and C# are like Hollywood blockbusters that cost incredible amounts of money to produce. Python and Ruby are like small independent films that are produced on a tiny budget but are often more creative and interesting than the films produced by Hollywood.
Ruby is the brainchild of a Yukihiro Matsumoto, also known as "Matz". He has said that one of his primary goals was to create a language that programmers would find fun to use. Given what I've seen and heard about Ruby, my opinion is that he has succeeded.
We spent most of the lecture in irb seeing how Ruby works. I won't try to recreate that in the notes, but I'll mention some of the key points to remember: