[As described below, this is Part B of a 3-part course. Participants should complete Part A first -- Part B "dives right in" and refers often to material from Part A.]
This course is an introduction to the basic concepts of programming languages, with a strong emphasis on functional programming. The course uses the languages ML, Racket, and Ruby as vehicles for teaching the concepts, but the real intent is to teach enough about how any language “fits together” to make you more effective programming in any language -- and in learning new ones.
This course is neither particularly theoretical nor just about programming specifics -- it will give you a framework for understanding how to use language constructs effectively and how to design correct and elegant programs. By using different languages, you will learn to think more deeply than in terms of the particular syntax of one language. The emphasis on functional programming is essential for learning how to write robust, reusable, composable, and elegant programs. Indeed, many of the most important ideas in modern languages have their roots in functional programming. Get ready to learn a fresh and beautiful way to look at software and how to have fun building it.
The course assumes some prior experience with programming, as described in more detail in the first module of Part A. Part B assumes successful completion of Part A.
The course is divided into three Coursera courses: Part A, Part B, and Part C. As explained in more detail in the first module of Part A, the overall course is a substantial amount of challenging material, so the three-part format provides two intermediate milestones and opportunities for a pause before continuing. The three parts are designed to be completed in order and set up to motivate you to continue through to the end of Part C.
Week 1 of Part A has a more detailed list of topics for all three parts of the course, but it is expected that most course participants will not (yet!) know what all these topics mean.

KR

I'm a pretty experienced C/C++ programmer and this course has been a brilliant introduction into functional programming and the underlying philosophies underpinning programming languages in general.

JM

May 09, 2018

Filled StarFilled StarFilled StarFilled StarFilled Star

DO THIS COURSE. It brings together everything in part A. In particular, the final shorter week on the differences between Statically and dynamically typed languages is mind-bending and brilliant.

Na lição

Section 5 and Homework 4 (First Module with Racket)

Let's get started programming with Racket and then learning idioms related to delaying evaluation. The welcome message has a few additional comments about picking up a new language and how to approach the homework assignment, so let's get started...

Ministrado por

Dan Grossman

Professor

Transcrição

[MUSIC] In this segment and the next we're going to talk about streams which are a different programming idiom that also need some notion of delaying evaluation. And the implementation will use thunks in order to accomplish that. So a stream is a word that we use in computer science to mean an infinite sequence of values, all right? Something that can go on for as long as you need. It behaves like something infinitely big. Now one thing about infinite size things, you can't actually make them, right? We need something that's going to represent something that could go on forever. And the key idea we're going to use is to use a thunk to delay the evaluation of most of the sequence. And only generate a prefix of the sequence that some other computation needs. We're not going to need any new language constructs. This is just a programming idiom using thunks and things like that. But it's a powerful concept for dividing labor up in a way that works in a lot of different software systems. So the idea is that the pro part of the program producing a stream, creating a stream, knows how to create any number of values you need, but does not know how many you need. Whereas the stream consumer can ask for these values as it goes along, without knowing anything about the process that is generating them. So it turns out that this comes up a lot in software systems. It's okay if you're not familiar with any of these examples, but I thought I would mention them for those of you who are. One way is if you're implementing code that needs to respond to a whole bunch of user events, mouse clicks, keyboard presses, things like that. We saw earlier in the course we could do that with call backs, but another way we could do it is to think of that, as some stream of events, we'll ask for each one as we need it, and then will compute some result with that thing so far. And someone else would generate those events as they occur. If you've ever programmed with pipes in the Unix shell system, it turns out, that the second command pulls data from the first command as it needs it. So it views the first command as a stream, and the first command's output is generating that stream. There's also a nice connection with electrical engineering and circuits, but if you think of a timed circuit with feedback, you can think of the different output values it's sending on its output wires as forming an infinitely long sequence and then the circuits reading those values can read the ones that they're interested in. Anyway just optional things showing this is kind of a universal concept even if you find it a bit abstract. You also see some simpler and more fun examples on the homework assignment associated with this material. Okay, so we want to represent a stream in some way that we don't actually generate an infinitely long list or something like that. So, here's how we'll do it. We're going to represent a stream as a thunk. So, a stream will just be a thunk. But not just any kind of thunk. A thunk that when you call it, gives back a pair where the car is the next thing in the sequence. The first thing in the sequence. And the cdr is a steam for values 2 through infinity. So it is a stream that if you use it, you will get the next value. In this segment I'm just going to show you how to use these things, then in the next segment we'll see how to define our own. Using them usually helps explain what they are and get a little better sense before we try to create them. So, I've already loaded the file where I've created the streams I will show you in the segment. And one of the streams is the infinite sequence of powers of 2. So the first thing that this thing returns, I forget if it starts at 1 or 2 and then 4 and then 8 and then 16, and then 32, on forever. Because we don't know how many powers of 2 we need. But when I said powers of 2, as you saw here, all I got back was a procedure because our streams, our thunks, that when you call them return a pair. So how do you call a thunk? You put it in parenthesis, powers of 2. And look at that, I got back a pair whose first component is 2, so I did set it up to start at 2. And the second component is another procedure, it turns out that's a thunk. So if I wanted the first thing in the sequence, I could just say car of, calling that, and if I wanted the second sequence, well let's think about this. I need to call the cdr, to get another stream, a stream is a thunk so I need to call it. And then I need the car of that, and that gets me 4. What if I wanted the next element of the sequence? Well, this thing is 4, the cdr is another stream, a stream is a thunk, so you call it. That gives back a pair, and I need the car of that, and that would give me 8. Now of course, we wouldn't keep programming like this to get 16 or 32. The idea is we would have some sort of recursive function that is passing this next stream onto, say, some recursive call. And then we apply that stream to get a pair, and we take car to get the next thing. And so if you wanted to, say, add up the first 100 powers of 2, you would just have some little recursive function that would be using this stream as you go along. So what I thought I would do instead of showing you that, is show you something even more general. Let's define a recursive function that I'll call number-until, that's going to take in a stream and a function which I'll call tester. All right. And the idea of what this is going to do is it's going to count how many stream elements you need to process before tester returns true for the first time. So if tester never returns true, it will go into an infinite loop. But otherwise we'll stop as soon as we get our first true and we'll return the count of how many we've gotten. So I'm going to do this with a little tail recursive helper function. So I have a little letrec here. So I'm going to take in my current stream, the stream that has all the elements I haven't processed so far. My cumulator, which is my answer so far, and I'm going to have to put some stuff in here. And then I'm just going to call f with the stream I started with and 1, that's my initial cumulator. So now the idea is all the body of f has to do where I've left this... is see, well, what does tester have to say on the first element of the stream? If that's true, return answer. Otherwise, call f again with one more and with the tail, if you will, of the stream, the rest of the values. So here's how I'm going to do this. Let's first of all call that stream. We know a stream is a thunk, all right? I'll get rid of these so you can see it. So I know a stream is a thunk, so if I call it, I should get back this pair, right, of the first element and then the stream that is the rest of the elements. Okay. Now that I have that pair, let's call tester on the car. If I get back true, I'm done. Return ans. All right, otherwise, call f on the cdr of the pair, that's my new stream, right. Don't call it yet, right? We don't want a pair. This would be a pair but f expects the stream and then f itself calls that thunk to get back a pair. So just with the cdr, and then one more for the accumulator, that's my call to f, that finishes my if, my let, my definition of the lambda for f, and this let rec that I then use to start by calling f with the stream that was past the number until, and 1. And then I need to end my define. My letrec and my define, and now I've just defined a function, that's all I did. So now if I call number-until with the stream powers-of-two, all right, and how about a little function that takes in a number and says, does that number equal 16. I get back 4, it took me 4 times through the stream until I got something that equaled 16. Let's have a little more fun. How about I keep going until I get a number that is bigger than this number. The great thing about powers-of-two is they grow really fast. So that took 339 tries. If I had come up with a number 10 times bigger, it will take 343 tries. And 10 times bigger than that it will probably take 346 tries because powers-of-two multiply really fast. Let me point out that when you're programming streams, you tend to make lots of mistakes with parentheses. Think very carefully about, do I want to pass in a stream? Or a pair that I get back when I call the thunk? If I get this wrong and I put parentheses here, I end up passing to number-until a pair. And therefore, right here, you can just see at the top of this stream. That's going to be a pair when you try to treat a pair as a function, you get a big nasty error message. Says procedure application: expected procedure, given the pair 2 and some string. So you have to think very carefully about the difference between a thunk and a pair. And if you do that you can do some beautiful programming by using streams with beautiful recursive functions to get interesting results.