diff --git a/chapter10/README.md b/chapter10/README.md index 7f7fdd4..b992df9 100644 --- a/chapter10/README.md +++ b/chapter10/README.md @@ -794,30 +794,153 @@ end #### What can see a local variable? -**Regular variables** are normally referred to as **local variables**. They can be 'seen' by any object _inside_ the object in which they were defined: +**Regular variables** are normally referred to as **local variables**. Here, we're in imperative-land: telling the computer what to do, line-by-line. + +If a line defining that local variable _has already been executed_, that local variable is available to anything that wants it. ```eval-ruby # Define a local variable in the main program object +my_variable = 1 + +# We can access the local variable here, because the line above was executed before this one +my_variable +``` + + + +This is the case even if the local variable was defined in a conditional branch that got executed: + +```eval-ruby +if true + # This conditional branch will be executed + my_variable = 1 +end + +my_variable +``` + +One gotcha – strange things happen if you define variables in branches that _don't_ get executed: + +```eval-ruby +if false + my_variable = 1 +end + +my_variable +``` +The program can still read the name: but the value is set to `nil`. +There's one really tricky thing about local variables, and it has to do with methods. Here it is: + +```eval-ruby +def my_method + my_variable = 1 +end + +my_variable ``` +Why can't the main object see the `my_variable` variable, even though it was defined in the lines above? The answer that most makes sense: `my_method` didn't get executed yet. We only declared it, but we didn't call the method. + +So we can solve it like this, right? + +```eval-ruby +# define the method +def my_method + my_variable = 1 +end + +# run the method, executing the procedure that defines my_variable +my_method +my_variable +``` -In programming, defining methods or variables on the main program object is called defining at **global scope**. Global, because it surrounds the entire program (sometimes it's called the '_universal_ scope'). But 'scope'? +Wrong. For some reason – even though the procedure `my_variable = 1` (inside the method `my_method`) has been executed – the main object still can't see `my_variable`. Why is this? ## Scope -In Ruby, **scope** is: 'whatever an object has access to'. Think of a sniper-rifle scope: when you look down it, you can only see a part of the world: the part of the world you can shoot. +Every time we write one of the following: + +- `def` +- `class` + +We 'open' something. -In general, we want to _minimise the scope_ of objects. We don't want them to have access to more of the program than they should. When we do something like this: +- `def` opens a method, so we can define a procedure inside. +- `class` opens a new class, so we can define methods inside. + +The keyword `end` then 'closes' that something you just opened. ```eval-ruby -named_object = Object.new +def average + # we opened the average method, so we can define procedures in it +end + +class Dog + # we opened the Dog class, so we can define methods in it +end +``` + +Variables that we define inside these things cannot be seen outside of them: + +```eval-ruby +def average + accumulator = 0 +end + +class Dog + some_variable = 1 +end + +puts accumulator +puts some_variable ``` -Everything in the program can now access and use the name `named_object`: so everything in the program can now access and use the object it references. This can make for a super-confusing message flow, as every part of the program tries to access names declared somewhere else. Grow beyond a few dozen lines of code, and it's going to get horrible. +The area of a program in which a variable can be read is called the variable **scope**. `def` and `class` are known as **scope gates**: when the program runs this instruction, it enters a new scope. Variables defined inside this scope cannot be read outside of the scope gate. Variables defined outside of the scope gate cannot be read inside it. + +```eval-ruby +my_variable = 1 + +def my_method + # I'm in a new scope gate! I can't read my_variables + my_variable + my_variable +end + +my_method +``` -This chapter +Here's a visual representation of scope: + + + +> Confused by the word 'scope'? Think of the scope on top of a sniper-rifle: when you look down it, you can only see a part of the world: the part of the world you can shoot. + +## Scope and parameters + +Scope is especially helpful when it comes to understanding method parameters. For each parameter, methods will define a local variable within their scope. The name of that variable will be set to the name of the parameter: + +```eval-ruby +age = 22 + +def age_reporter(number) + # Whatever we pass as an argument to age_reporter will be assigned to a local variable named 'number' + return "Your age is " + number.to_s +end + +age_reporter(age) +``` + +Since `def` is a scope gate, we can't read these local variables outside of them: + +```eval-ruby +name = "Sam" + +def say_hi_to(person) + return "Hi, " + person +end -* Creating your own object worlds \ No newline at end of file +# We can't read this +person +``` \ No newline at end of file diff --git a/chapter2/README.md b/chapter2/README.md index 2ac3687..f567e6c 100644 --- a/chapter2/README.md +++ b/chapter2/README.md @@ -282,4 +282,22 @@ warning: previous definition of ONE was here This makes sense for our purposes: we don't want anyone – be it another programmer or a user of the numeral calculator – the reassign the variables `one` through `ten` to any other numbers. By naming them `ONE` through `TEN`, we're making our intention clear: don't reassign these names, please: they're meant to stay this way. +## Modulo + +A peculiar math operation sometimes used in programming is the _modulo_. It divides one number by another, then gives the 'remainder': + +```eval-ruby +5 % 2 +``` + +5 divided by 2 is 2.5. Modulo says "5 divided by 2 is 2, with 1 remaining." + +Here's another example of the modulo. Play around with it until you understand what it's doing: + +```eval-ruby +6 % 3 +``` + +> 6 divided by 3 is 2. Since 2 is a whole number, modulo says "6 divided by 3 is 2, with 0 remaining." + > I've lied a bit about how numbers work in Ruby. Numbers aren't actually created at the same time as the main program function. In reality, an smaller, sub-world (a 'function') is created. When we type `100`, that function is executed in such a way as to return the number 100 to the world on-the-fly. It's a small distinction: but why does Ruby do this klind of on-the-fly generation? The answer is: this is a way to avoid slow program start-up, where the program has to generate loads of numbers before it can show the prompt. The reason we're not covering this in detail here is because this function isn't actually a Ruby function: it's a C function, which Ruby executes. You can learn more [here](https://stackoverflow.com/questions/3430280/how-does-object-id-assignment-work) if you're interested. diff --git a/chapter3/README.md b/chapter3/README.md index c494aba..fc2bf67 100644 --- a/chapter3/README.md +++ b/chapter3/README.md @@ -4,13 +4,11 @@ Our REPL provides us with a window through which we can interact with the progra ## Messages -We interact with the program world by sending it **messages**. In response to our messages, objects **return** something (the 'return value'). In our REPL, by default we're interacting with the Main Object (the 'Universe'). +We interact with the program world by sending it **messages**. In response to our messages, objects **return** something (the 'return value'). When we're using the REPL, the program world treats us, the programmer, as if we were just another object in the object world. - + -The main object will then figure out what to do with the message. Just as we, the user, send the main object messages, so the main object can send other objects messages. - -Here's a visual depiction of us, the programmer, interacting with the REPL by requesting an object by name (`one`). Provided that we have defined this variable, the REPL goes and fetches the object referenced by that name: +Here's a visual depiction of us, the programmer, interacting with the REPL by requesting an object by name (`one`). Provided that we have defined this variable, the program world goes and fetches the object referenced by that name: ![Asking the program world for a variable, and the world retrieving the object via pointer](../images/2-retrieving-object-by-variable.gif) @@ -54,7 +52,7 @@ Other times, a messaged object needs to know something else to answer the messag > => 9 ``` -It's formally-correct (like dot syntax) to wrap an argument in **parentheses** `()`. But, sometimes that's optional: +Just like dot syntax is formally-correct, it's formally-correct to wrap an argument inside **parentheses** `()` (I've done that above). But, sometimes, that's optional: ```eval-ruby # 2 is the argument @@ -67,74 +65,41 @@ It's formally-correct (like dot syntax) to wrap an argument in **parentheses** ` => 9 ``` -> So why does Ruby permit us to use this non-dot-syntaxy way of sending messages? In short, convenience.Ruby was designed to read as similarly to English as possible. These sorts of 'edge cases' – where Ruby syntax differs from normal – are rare. They're referred to as 'syntactic sugar' – because they are, quite literally, _sweeteners_ on top of the rules of the language. +> So why does Ruby permit us to use this non-dot-syntaxy way of sending messages? In short, convenience. Ruby was designed to read as similarly to English as possible. These sorts of 'edge cases' – where Ruby syntax differs from normal – are rare. They're referred to as 'syntactic sugar' – because they are, quite literally, _sweeteners_ on top of the rules of the language. - _**Convert your numeral calculator to use dot syntax with parentheses.**_ -> We'll be using dot syntax with parentheses from now on. - -## Chaining messages - -It's perfectly OK to send lots of messages to objects, one after the other: - -```irb -> four.+(five).+(seven).-(one) -=> 15 -``` - -This 'multiple messages' is referred to as **chaining**. This works because, read left-to-right, each `object.message` is replaced by the value returned from sending that message to that object: - - - -- We say "hey `4`, go get us the float with a value equal to your value". -- `4` says "OK, here's `4.0`". -- Then we say "hey `4.0`, divide yourself with `5`". -- `4.0` says "OK, that's `0.8`". +> We'll be using dot syntax with parentheses for now, so we can get used to writing formally-correct code. -> The principle that "`object.message` gets replaced by the return value, which can be a new `object` for a new `message`" is called **referential transparency**. - -## Interfaces +## Making random numbers -What determines the possible things that can be sent to a particular object? If we wanted to, could we try: +If we don't specify an object to send a message, by default it'll be sent to `main`, the main program object: -```irb -> 1.any_message_i_want -=> ??? +```eval-ruby ++(3) ``` -Of course not. Objects have a limited number of possible messages they can be sent. In fact, it's this very set of messages that mostly determines what an object _is_ in Ruby. For instance, if an object responded to a set of messages like this: +The main program object has a few useful methods. You've already met one: `puts`. Another useful one is `rand`: -```irb -> object.bark -=> "woof!" -> object.wag_tail -=> "wag wag wag" -> object.dream_about_chasing_things -=> "*twitches*" +```eval-ruby +rand ``` -Then you could pretty reasonably figure out what that object is (a dog, if you were guessing). - -The set of messages that can be sent to an object is called its **interface** (from _inter_ (between) and _face_ (form) – so literally the shape of the space 'between objects'). A message that can be received by an interface is called a **method**. - -Interfaces are a list of object methods. Methods make up the interfaces of objects. - - - -How do we find out what an object's interface is? Two ways: +Each time you run `rand`, you'll get a random float between zero and one. If you give `rand` an integer argument: -1. All Ruby objects have a method called `methods` defined on their interface. If you call it, you get a list of every method defined on that object's interface. -2. Object interfaces are detailed in documentation, along with examples of how to use those interfaces. For instance, here's the [documentation for integers](https://ruby-doc.org/core-2.2.0/Integer.html). +```eval-ruby +integer = 6 -> If you're using the docs, right now we're using the word _interface_ interchangeably with _public instance methods_. We'll go deeper in the next module! +rand(integer) +``` -- _**Find out every method defined on the interface of the object referenced by `1` (you don't have to memorise them).**_ +You'll get a random number between zero and `integer - 1` (so here, `0` to `5`). -> Many beginner programmers feel like they have to memorise every method on an object's interface. That's not true. While there are core methods you should learn – like `+`, `-`, and so on – it's far more useful to get good at looking methods up. One benefit to Ruby methods is they're well-named: they usually do just what they say on the tin. +- _**Write a program that rolls a six-sided die and returns the result. This program should be runnable from the command-line.**_ ## Integers and floats -Something pretty weird happens when we do divison with our numeral calculator: +Something pretty weird happens when we do division with our numeral calculator: ```irb > nine./(two) @@ -183,46 +148,102 @@ Of course, this is going to get pretty ugly for our poor numeral calculator. We => 0.8 ``` -## Modulo +## Interfaces -A peculiar math operation sometimes used in programming is the _modulo_. It divides one number by another, then gives the 'remainder': +What determines the possible messages that can be sent to a particular object? If we wanted to, could we try: -```eval-ruby -5 % 2 +```irb +> 1.any_message_i_want +=> ??? ``` -5 divided by 2 is 2.5. Modulo says "5 divided by 2 is 2, with 1 remaining." - -Here's another example of the modulo. Play around with it until you understand what it's doing: +Of course not. Objects have a limited number of possible messages they can be sent. In the case above, we'd get a useful error message telling us this: ```eval-ruby -6 % 3 +1.any_message_i_want ``` -> 6 divided by 3 is 2. Since 2 is a whole number, modulo says "6 divided by 3 is 2, with 0 remaining." +In fact, the very set of messages to which an object can respond mostly determines what an object _is_ in Ruby. For instance, if an object responded to a set of messages like this: -## Making random numbers +```irb +> object.bark +=> "woof!" +> object.wag_tail +=> "wag wag wag" +> object.dream_about_chasing_things +=> "*twitches*" +``` -If we don't specify an object to send a message, by default it'll be sent to `main`, the main program object: +Then you could pretty reasonably figure out what that object is (a dog, if you were guessing). -```eval-ruby -+(3) +Likewise, if an object responded to messages like this without throwing errors: + +```irb +> object.walk +=> "waddle waddle" +> object.quack +=> "waak waak" ``` -The main program object has a few useful methods. You've already met one: `puts`. Another useful one is `rand`: +You could guess that the object is a duck. This principle is known as **duck typing**. That is: _if it walks like a duck, and it quacks like a duck, it's a duck_. Ruby cares less about what objects are, in and of themselves, and more about what sorts of messages they respond to. -```eval-ruby -rand +Here's a more realistic example from a real-life program: + +```irb +> object.calculate_average +=> 17.4 ``` -Each time you run `rand`, you'll get a random float between zero and one. If you give `rand` an integer argument: +Again, we can guess that `object` is probably some kind of 'calculator'. -```eval-ruby -integer = 6 +The set of messages that can be sent to an object is called its **interface** (from _inter_ (between) and _face_ (form) – so literally the shape of the space 'between objects'). -rand(integer) + + +## What happens when we send an object a message? + +Every message that can be received by an object has some sort of **procedure** attached to it. This is a set of instructions that is read by the machine, to produce some desired outcome. We'll go into much more detail about how to write these procedures later. + +Like many program objects, these procedures have names. A named procedure is called a **method**. Executing the procedure is referred to as _calling the method_. + +> An interface is a list of all the things an object can do: all the object's procedures. In other words, an interface is a list of object methods. + +## Investigating object interfaces + +How do we find out what an object's interface is (i.e. what messages they can respond to, or what methods they have)? Two ways: + +1. All Ruby objects have a method called `methods` defined on their interface. If you call the `methods` method, you get a list of every method defined on that object's interface. +2. Object interfaces are detailed in documentation, along with examples of how to use those interfaces. For instance, here's the [documentation for integers](https://ruby-doc.org/core-2.2.0/Integer.html). + +> If you're using the docs, right now we're using the word _interface_ interchangeably with _public instance methods_. + +- _**Find out every method defined on the interface of the object referenced by `1`.**_ + +> Many beginner programmers feel like they have to memorise every method on an object's interface. That's not true. While there are core methods you should learn – like `+`, `-`, and so on – it's far more useful to get good at looking methods up. One benefit to Ruby methods is they're well-named: they usually do just what they say on the tin. As a result, you can usually have a guess at the method you need before needing to look it up. + +## Chaining messages + +It's perfectly OK to send lots of messages to objects, one after the other: + +```irb +> four.+(five).+(seven).-(one) +=> 15 ``` -You'll get a random number between zero and `integer - 1` (so here, `0` to `5`). +As the computer goes through the line, each `object.message` statement is replaced by the value returned from sending that message to that object: -- _**Write a program that rolls a six-sided die and returns the result. This program should be runnable from the command-line.**_ + + +This 'queueing up of messages' is referred to as **chaining**. + +- `four`: We say "hey program, go get us the object referenced by the name `four`". +- _The program says "OK, here's `4`"._ +- `.+(five)`: We say "hey `4`, add the value of the object referenced by the name `five` to your value". +- _`4` says "OK, here's `9`"._ +- `.+(seven)`: We say "hey `9`, add the value of the object referenced by the name `seven` to your value". +- _`9` says "OK, here's `16`"._ +- `.-(one)`: We say "hey `16`, add the value of the object referenced by the name `one` to your value". +- _`16` says: "OK, here's `15`"._ +- _The program says "I'm done with this line! Here's what it evaluated to: `15`"._ + +> The principle that "`object.message` gets replaced by the return value, which can be a new `object` for a new `message`" is called **referential transparency**. diff --git a/chapter4/README.md b/chapter4/README.md index 61138eb..6a07034 100644 --- a/chapter4/README.md +++ b/chapter4/README.md @@ -14,9 +14,17 @@ So far, we've looked a lot at what exists in the program world (objects), and ho ## Going inside methods -Methods contain pre-written instructions – **procedures** – for the computer. Just as we have used instructions – line-by-line – to control our program world and everything in it, so object methods contain pre-written lines of instructions to control themselves, and everything inside them. We can cause the object to execute its procedure by sending it a message with the same name as the method that contains that procedure. +Methods contain pre-written instructions – **procedures** – for their object to do. -For example, let's say we're an object in the program world. We want a cake: but we don't know how to bake the cake. Fortunately, there's another object in this world, `dad`, that does know how. +Just as we have used line-by-line instructions to control our program world (and objects in it), so object methods contain pre-written lines of instructions to control their objects, and everything inside them. You could think of them as being 'programs within programs'. + +We can cause an object to execute a procedure by _calling a method_ defined on that object. We call methods by sending objects messages. + +> If we don't designate an object to send a message to, and just send a message, we send that message to the main object. The main object will happily carry out procedures, like any other object. + +## What's a procedure? + +Let's say we're an object in the program world. We want a cake: but we don't know how to bake the cake. Fortunately, there's another object in this world, `dad`, that does know how. So, we send `dad` a message: "Hey Dad, can you bake me a cake?". Maybe we ask this a lot (and we're rude), so we'll just shorten the message we send to `cake`. When `dad` receives the `cake` method, he immediately executes his stored procedure. That procedure looks something like this: @@ -32,11 +40,31 @@ Dad goes off and does that, and once he's done, he `return`s the cake to us. Let ![A flowchart of Dad's cake recipe](../images/4-flowchart-cake.png) +> Procedures can be thought of as cooking recipes, which contain steps to produce a result. + +Here's a code-y version of what `dad` just did. I'm using a bunch of program structures we'll see later: don't sweat it if this makes only vague sense to you right now: + +```ruby +bowl = [] +bowl.push(flour) +bowl.push(eggs) +bowl.push(sugar) + +mixture = bowl.contents +mixture.bake + +return mixture +``` + +> The keyword `return` says "whatever follows is the return value from this procedure". More on this further down. + ## Conditional procedures -Procedures are about more than just executing one set of instructions, though. Right now, `dad`'s `cake` procedure can't handle, say, not having any flour. It'll just blow up and crash – or worse, Dad will keep trying to make the cake without having any flour, resulting in an eggy, sugary mess (or, at best, some sort of meringue). +Procedures are about more than just unquestioningly executing one set of instructions. + +What happens if `dad` tries to execute his `cake` procedure without any flour? It might just blow up, and crash Dad. Or worse: Dad might keep trying to make the cake without having any flour, resulting in an eggy, sugary mess (or, at best, some sort of meringue). -We'd like Dad to be able to stop his procedure if he doesn't have any flour. And we'd like him to `return` something to us in this case (maybe `0`, representing an 'error code' we agreed with him earlier), so we know what's gone wrong. Let's amend the first few procedures: +We'd like Dad to be able to stop his procedure if he doesn't have any flour. And we'd like him to give us a return value in this case (maybe `0`, representing an 'error code' we agreed with him earlier), so we know what's gone wrong. Let's amend the first few parts of the procedure: 1. Get bowl. 2. _If flour exists_ add flour. @@ -47,27 +75,27 @@ We'd like Dad to be able to stop his procedure if he doesn't have any flour. And 7. _If flour exists_ return mixture. 8. _If flour does not exist_ return `0`. -Steps 2 through 8 will now only be executed by Dad if flour exists. If flour doesn't exist, he'll jump straight to step 9, and return `0`. Since we know what `0` means when we ask Dad to `cake` (it means there's no flour), we can make a decision about what to do next. Here's the new flowchart: +Instructions 2 through 8 will now only be executed by Dad if flour exists. If flour doesn't exist, he'll jump straight to step 9, and return `0`. Since we know what `0` means when we ask Dad to `cake` (it means there's no flour), we can make a decision about what to do next. Here's the new flowchart: ![An updated flowchart, with a conditional](../images/4-flowchart-conditional-cake.jpg) -These sorts of 'conditional' procedures are how the majority of programs work. Depending on things happening, or not happening, programs can do different things. +These sorts of 'conditional' procedures are how the majority of objects – and hence the majority of programs – work. Depending on things happening, or not happening, programs can do different things. > Conditional procedures depend on the outcome of a **condition**. Here, the condition is 'does flour exist?' -Let's investigate a more code-y example. How does `1` know to return `true` here? +Let's investigate a more code-oriented example. How does `1` know to return `true` here? ```eval-ruby 1.positive? ``` -And `-1` knows to return `false`? +And yet `-1` knows to return `false`? ```eval-ruby -1.positive? ``` -Let's go inside the `positive?` method defined on the interface of `1`: +There must be some procedure inside the `positive?` method defined on integer objects. This procedure determines whether to return `true`, or `false`. Let's go inside the `positive?` method defined on the interface of `1`: @@ -87,22 +115,29 @@ So what the heck do these instructions do? Let's identify the parts of this code: -- `self` is the object that the instructions are within (i.e. the object on which this method is defined). In this case, since we're 'inside' `1`, `self` is `1`. +- `if` is an instruction to look at the statement that follows. If that statement returns anything _not false_, the instructions following it are executed. If the statement returns anything _false_, look for an `else`, and execute the instructions following that instead. +- `self` is the object in which this procedure will be executed (i.e. the object on which this method is defined). In this case, since we're 'inside' `1`, `self` is `1`. - `self > 0` is a statement comparing `self` with `0`. It's: - `true` if `self` is greater than `0`. - `false` if `self` is less than or equal to `0`. -- `if` is an instruction to look at the following statement. If it's _not false_, execute the following instructions. If it _is false_, look for an `else`, and execute the instructions following that instead. -- `end` says "OK, return to executing every line as normal, thanks." +- `end` says "OK, return to executing every line after this one as normal, thanks." -There are two ways this code could be executed. In one situation, the computer will `return true`. In the other, the computer will `return false`. The computer must make a choice, depending on the value of `self`. +There are two ways this code could be executed. -> A structure that tells a program to 'do one thing or the other' is called a **conditional**. `if`/`else` is a conditional. The statement which determines which 'branch' the program proceeds down is called a **condition**. Here, `self > 0` is the condition. +- If the `if` way is executed, the procedure will `return true`. +- If the `else` way is executed, the procedure will `return false`. -Let's depict the contents of the `positive?` method as a flow chart, and simplify the chart in the way the computer does. +Each possibility is called a **branch**. The object must make a choice as to which branch should be executed, depending on the return value of the statement `self > 0`. The return value of this statement depends only on the value of `self`: that is, the value of the object executing the procedure. + +> A structure that tells an object to 'do one thing or the other' is called a **conditional**. `if`/`else` is a conditional. The statement which determines which branch the object proceeds down is called a **condition**. Here, `self > 0` is the condition. + +## Working out which branch gets executed + +Let's depict the contents of the `positive?` method as a flow chart, and simplify the chart in the way the object does. ![A flowchart demonstrating the flow of information through the statement](../images/4-flowchart-self.png) -Since we're currently inside `1`, we can use referential transparency to replace `self` with `1`. That gives us: +Since we're currently inside a method defined on the object `1`, we can use referential transparency to replace `self` with `1`. That gives us: ```ruby if 1 > 0 @@ -124,7 +159,7 @@ else end ``` -Update the flow chart, with the obvious conclusion of the flow: +Let's update the flow chart, with the obvious conclusion of the flow: ![A flowchart demonstrating the flow of information through the statement, with 1 > 0 substituted for true](../images/4-flowchart-true.png) @@ -146,9 +181,9 @@ And that's how `positive?` works. ## Control Flow -The idea of _controlling which instructions get executed and which don't_ is called the **control flow**. There are other ways to adjust the control flow: we'll meet them in a minute. +The order in which an object executes instructions is called the **control flow**. We've just met one way of controlling this order: using a conditional to control which instructions get executed and which don't. There are other ways to adjust the control flow: we'll meet them in a minute. -- _**Draw out flow charts for the following sets of instructions. You should be able to guess the comparison operators. Your aim is to use referential transparency to replace the instructions, chunk-by-chunk, until your flowchart looks like the last one we made.**_ +- _**Imagine you are a program object. Imagine also that these instructions are defined a method defined on you. Draw out flow charts for the control flow of these instructions. You should be able to guess the comparison operators. Your aim is to use referential transparency to replace the instructions, chunk-by-chunk, until your flowchart looks like the last one we made.**_ ```ruby if 1 + 2 > 2 @@ -184,9 +219,17 @@ end ## More complex conditional procedures -So far, we've seen conditionals that contain the simplest possible conditional procedures: `return` a value depending on the truthiness or falsiness of some condition. +So far, we've seen conditionals with the simplest possible procedures on their branches: procedures that just `return` a value depending on the truthiness or falsiness of some condition: + +```eval-ruby +if 1 > 0 + return true +else + return false +end +``` -In the last example above, we saw that these conditional procedures could be statements, too: +In the last example you drew out above, you saw that these 'conditional procedures' could be statements, too: ```ruby if false @@ -198,39 +241,34 @@ else end ``` -The truth is: a procedure can be as simple or as complex as you like. For instance, you could write very complex conditionals: +The truth is: a procedure inside a conditional can be as simple or as complex as you like. For instance, you can assign names within conditional procedures: ```eval-ruby -if 1 > 0 - if 2 < 0 - return true - else - return 0 - end +a = 10 + +if a.integer? + a = a * -1 + return a else - return 15 + return a.to_f end ``` -You can assign names within procedures within conditionals: +You could write further conditionals within conditional procedures (although this can get confusing pretty quickly): ```eval-ruby -a = 10 - -if a.integer? - a = a * -1 - if a.positive? - return a +if 1 > 0 + if 2 < 0 + return true else - b = 2 + 2 - return b + return 0 end else - return a.to_f + return 15 end ``` -It's even commonplace to make calls to other objects within conditionals: +It's even commonplace to make calls to other objects within conditional procedures: ```ruby cake = dad.cake @@ -243,35 +281,44 @@ else end ``` -Finally, it's perfectly OK to have an `if` statement without an `else`: +If there are more than two possible branches, you can use an `elsif` statement in between an `if` and an `else`: ```eval-ruby -number = 1 +number = 0 if number.positive? - return number + return true +elsif number.negative? + return false +else + return 0 end ``` -And, if there is more than one condition, you can use an `elsif` statement in between an `if` and an `else`: +Finally, it's perfectly OK to have an `if` statement without an `else`: ```eval-ruby -number = 0 +number = 1 if number.positive? - return true -elsif number.negative? - return false -else - return 0 + return number end ``` > Play with each of the examples in the REPL above until they make sense. You can always refresh the page if you messed something up! -## Comparison operators +## Writing conditions: Comparison + +Here are the sorts of conditions we might meet in a program: + +- If the stock of products is less than 100, order more products. +- If this password matches the password on record, log the user in. +- If the player's score is greater than or equal to 501, they win. +- If the user has no more money, kick them off the table. + +These sorts of conditions are known as **comparison** conditions, because they compare one thing with another. -We've seen the comparison operators `>` and `<`. Here are the rest: +We've just met the comparison operators `>` and `<`. Here are the rest: ``` == @@ -280,23 +327,31 @@ We've seen the comparison operators `>` and `<`. Here are the rest: <= ``` -- _**Play in the REPL below to figure out which comparison operator means what.**_ +- _**Play with the code example below to figure out which comparison operator means what.**_ ```eval-ruby 1 > 0 ``` -## `true` and `false` +> Comparison operators are methods defined on, among other things, integers. Don't believe me? Try changing `1 > 0` to `1.>(0)` in the code example above. + +## Writing conditions: Logical operations What the heck are these things `true` and `false`, anyway? -They're objects, of course! And, like `1`, `2`, `3`, and all the other integers, they have meaning in the program world. They represent _boolean values_ in Ruby. `true` and `false`: +They're objects, of course! And, like `1`, `2`, `3`, and all the other integers, they have meaning in the program world. They represent _boolean values_. `true` and `false`: * Know about the truth or falsity of things, and * Know how to interact with each other. `true` and `false` are used to do logic: often, to manage the control flow. +Here are some logical conditions we might meet in a program: + +- If the user logs out and their cart isn't empty, send them an email to log back in and buy stuff. +- If the player is bankrupt or the player is out of pieces, send them a 'you lose!' message. +- If the user is not logged-in and a member, don't show them the member's area. + In Ruby, the logical operators include: ``` @@ -305,15 +360,17 @@ In Ruby, the logical operators include: ! ``` -Play in the REPL below to figure out what they mean: +- _**Play with the code example below to figure out which logical operator means what.**_ ```eval-ruby !true ``` -## Parentheses +> Are logical operators methods? On which objects are they defined? -_Generally speaking_, Ruby will evaluate things in parentheses `()` before anything else in a line: +## Manually managing control flow with parentheses + +_Generally speaking_, an object will evaluate statements in parentheses `()` before any other statement in an instruction: ```eval-ruby !true && false @@ -337,11 +394,19 @@ Here's another example: ## Repeating instructions -So far we've met one way of controlling the flow of information in a program: conditionals. +So far we've met one way of managing the control flow for an object: conditionals. ![A flow of control graph, demonstrating conditional logic](../images/4-conditional-graph.jpg) -What about if we wanted to do a different kind of instruction? One where we don't want the computer to skip instructions: we want to repeat them. +What about if we wanted to do a different kind of instruction? One where we don't want the object to skip instructions in a program branch: we want it to _repeat_ instructions. + +Let's imagine an object that prints `1` to the console forever: + +```ruby +eternal_printer.print_1_forever +``` + +Here's how the procedure inside the `print_1_forever` method defined on `eternal_printer` might look: ```ruby while true do @@ -349,13 +414,17 @@ while true do end ``` -The code above will output 1, forever. The procedure – `puts 1` – will keep being called. That's because every time the computer reaches the `end` statement, it'll return to the `while true` statement: +The code above will output 1, forever. The procedure – `puts 1` – will keep being called. That's because every time the `eternal_printer` object reaches the `end` statement, it'll jump back up to the `while true` statement: ![A flow of control graph, demonstrating a while loop](../images/4-while-graph.jpg) -> Notice that we wrap the procedure, `puts 1`, in a `do...end` structure. This is a structure that Ruby uses to formally designate a procedure. We don't use it with `if`, but we do with most other flows of control. It's actually optional for `while`, but I suggest using it for clarity. The takeaway is "everything between `do` and `end` is a procedure". +> Notice that we wrap the procedure, `puts 1`, in a `do...end` structure. This is a structure that Ruby uses to formally designate a procedure. We don't use it with `if`, but we do with most other control flows. It's actually optional for `while`, but I suggest using it for clarity. The takeaway is "everything between `do` and `end` is a procedure". -Of course, being stuck in such an _infinite loop_ isn't usually that helpful – we want to quit the loop at some point, so we can keep on executing other instructions in the program. Fortunately, Ruby gives us a way to exit from a `while` loop: +Of course, the `eternal_printer` object being stuck in such an _infinite loop_ isn't usually that helpful – we probably want the object to quit its loop at some point, so we can set it to executing other program instructions. + +> In Ruby, only one object can run procedures at a time. Since an object executing a procedure in a `while true` loop will just keep repeating the procedure forever, this means that your whole Ruby program will never do anything else. Unless the procedure has some kind of output – like printing to the console – this will give the appearance of your program 'freezing'! + +Fortunately, Ruby gives us a way to exit from a `while` loop: ```ruby while true do @@ -364,13 +433,13 @@ while true do end ``` -This `break` keyword will jump out of the loop. Ruby will read the program line-by-line: +This `break` keyword will jump out of the loop. The object will execute the procedure line-by-line: - `while true do`: Set up a `while` loop. Keep it going forever. Run the procedure inside the loop. - `puts 1`: Print `1` to the console. - `break`: Exit the loop. -Anything _after_ a `break` won't be read. It works like a loop-style `return`: +Anything _after_ a `break` won't be executed by an object. In the example below, `2` will never be printed, because the instruction to do so comes after the instruction to `break` out of the loop. ```ruby while true do @@ -380,33 +449,50 @@ while true do end ``` -> `2` will never be printed, because it comes after the `break` statement. - #### Using `while` and `break` to make games -This sort of structure – `while true` with `break` – is especially useful for making games. It's especially powerful when combined with a conditional `break`: that is, a `break` that only happens under certain conditions. A regular condition for this kind of conditional `break` is the existence of a winner: +This sort of structure – `while true` with `break` – is especially useful for making games. It's especially powerful when combined with a conditional `break`: that is, a `break` that only happens under certain conditions. A regular condition for this kind of conditional `break` is the existence of a winner. + +Let's imagine that we have a `game` object with a method, `start`: + +```ruby +game.start +``` + +Inside the `start` method, there might be a procedure like this: ```ruby while true do - player_1_play - player_2_play + # run a method on a player object + player_1.play + # run a method on the other player object + player_2.play - if winner_exists + # wins? is another method on player object, that returns true if they won + if player_1.wins? + break + elsif player_2.wins? break end end ``` -> If `winner_exists` returns false, the `break` will never be executed, returning to the top of the loop. +> If both `player_1.wins?` and `player_2.wins?` is false, the `break` will not be executed, returning to the top of the loop so both players can play again. Each time `game` passes through the loop is a 'turn'. + +Most games are built around loops, with some 'break' condition being 'the end of the game': + +- **Chess** is a loop that alternates turns between users forever, and `break`s when there is checkmate or stalemate. -Most games are loops, with some 'break' condition being 'the end of the game': +```ruby +chess.start +``` -- **Chess** is a loop that alternates turns between users forever, and `break`s when there is checkmate or stalemate: +Inside the `start` method defined on the `chess` object: ```ruby while true do - player_1_play - player_2_play + player_1.play + player_2.play if checkmate || stalemate break @@ -416,11 +502,17 @@ end - **Monopoly** is a loop that alternates turns between users forever, and `break`s when there is only one non-bankrupt player left. +```ruby +monopoly.start +``` + +Inside the `start` method defined on the `monopoly` object: + ```ruby while true do - player_1_play - player_2_play - player_3_play + player_1.play + player_2.play + player_3.play if only_one_non_bankrupt_player_left break @@ -428,12 +520,20 @@ while true do end ``` +> `only_one_non_bankrupt_player_left` could be another method defined on the `monopoly` object. + - **Football** is a loop that kicks a ball around between teams, accumulating goals, and `break`s when 90 minutes have passed. +```ruby +football_game.play +``` + +Inside the `play` method defined on the `football_game` object: + ```ruby while true do - kick_ball - maybe_score_goal + thierry_henri.kick_ball + eric_cantona.maybe_score_goal if ninety_minutes_passed break @@ -441,11 +541,29 @@ while true do end ``` -When being tasked with making a game, one of your first questions should be "what happens each game loop? And what's the break condition?". Simply by inventing new answers to these questions, new games are born. +> `ninety_minutes_passed` could be another method defined on the `football_game` object. + +When being tasked with making a game, one of your first questions should be "what happens each game loop? And what's the break condition?". + +> Simply by inventing new answers to these questions, new games are born. Invent one! #### Using accumulators -`while` loops are super-useful for repeatedly doing something to some existing variable: +An object might want to keep track of some value during a `while` loop. The object can't just define a variable inside the loop: + +```ruby +while true do + goals_scored = 0 + + if eric_cantona.score_goal + goals_scored = goals_scored + 1 + end +end +``` + +Because each time the loop runs, the variable will be reset to its initial value. No matter how many times `eric_cantona` scores a goal, `goals_scored` will always be reset to `0`. + +If we want to keep track of a value during a loop, we have to declare a variable outside of the loop: ```ruby my_number = 0 @@ -462,8 +580,6 @@ Each time the `while` loop above runs: - `my_number = my_number + 1`: `my_number` will increase in value by `1`. - `puts my_number`: `my_number` will be printed to the console. -> Run this code at your peril! Infinite loops have interesting consequences on different machines. - In short: `my_number` will count upwards, forever. So how about if we just wanted to print out the first ten numbers, then stop? ```ruby @@ -479,18 +595,18 @@ while true do end ``` -Now that we've added the conditional, each time the `while` loop above runs: +Now that we've added the conditional, each time the `while` loop above runs the object does the following: -- `my_number = my_number + 1`: `my_number` will increase in value by `1`. -- `puts my_number`: `my_number` will be printed to the console. -- `if my_number == 10`: only execute the following code if `my_number` is equal to `10`. +- `my_number = my_number + 1`: increase the value of `my_number` by `1`. +- `puts my_number`: print `my_number` to the console. +- `if my_number == 10`: only execute the following procedure if `my_number` is equal to `10`. - `break`: if the above condition was met, exit the loop. -This technique is called using an **accumulator**. The accumulator 'keeps track' of what's going on in the `while` loop, and allows us to exit the loop in certain conditions. Our accumulator above keeps track of how many times the `while` loop has run. +This technique is called using an **accumulator**. The accumulator 'keeps track' of what's going on in the `while` loop, and allows the object to exit the loop in certain conditions. Our accumulator above keeps track of how many times the `while` loop has run. #### `while` loop conditions -We've seen that we can make a `while` loop run forever with `while true`. This 'forever running' happens because `while` loops are dependent on _conditions_. This while loop will never run at all: +We've seen that an object can execute procedures in a `while` loop forever, if we tell it to run `while true`. How about if we tell it to run `while something else`? ``` while false do @@ -498,7 +614,9 @@ while false do end ``` -And this while loop will print the integers 1 to 10 to the console before quitting the loop, by using an accumulator: +The while loop above will never run at all: `while` loops are dependent on _conditions_. + +By using an accumulator, we can tell an object executing the following while loop to print the integers 1 to 10 to the console, then to quit the loop. ```ruby number = 0 @@ -509,20 +627,51 @@ while number <= 10 do end ``` -- _**Using a while loop, print the numbers 10 to 100 to the console.**_ +- _**Using a while loop, instruct the main object to print the numbers 10 to 100 to the console.**_ I'd recommend building your `while` loops with `break`s until you're familiar with how they work, then seeing if you can turn those loops into conditional ones. -#### Recap +## `return` + +`return` says to an object: -We've just seen two ways of controlling a program loop: +- Stop from executing this procedure. +- Return this value as the 'return value' from the procedure. -- Using a conditional `break` with an accumulator. -- Using a conditional `while` loop. +It's typically used inside a method, to say "this is what should come back from calling this method". However, we can use it to manage the control flow: because _instructions after a return will never be executed_: + +```ruby +if true + puts 1 + return 0 + + puts 2 +end +``` + +Breaking down the control flow line-by-line, these are instructions to the main object as follows: + +- `if true`: Always execute the following branch. +- `puts 1`: Print `1` to the console. +- `return`: Stop execution here, return `0` to the object that called you (if that's a REPL, return `0` to the REPL) + +Notice that I didn't even write the instruction to `puts 2`. That's because it never gets executed, because `return` stopped the main object from executing any further parts of the procedure. + +- _**Play with the code example below. Can you make it return `b`, instead of `a`?**_ + +```eval-ruby +if true + a = 1 + return a + + b = 2 + return b +end +``` ## Combining flows of control -The majority of _flow of control_ combines loops and conditionals. Here's a simple example, that prints only even numbers under 101 to the console: +The majority of code for managing control flow combines loops and conditionals. Here's a simple example, instructing the main object to print only even numbers under 101 to the console: ```ruby number = 1 @@ -536,11 +685,11 @@ while number < 101 do end ``` -- _**Write a program that prints only odd numbers under 100 to the console.**_ +- _**Instruct the main object to print only odd numbers under 100 to the console.**_ ## Random conditionals -Last module, we met `rand`, which generates random numbers. We can use it in conditionals if we want to produce random behaviour: +Last module, we met `rand`, which generates random numbers. We can use it in conditionals if we want objects to execute random branches. This simulates random behaviour: ```eval-ruby random_number = rand(10) diff --git a/chapter5/README.md b/chapter5/README.md index 0705a5d..362ae68 100644 --- a/chapter5/README.md +++ b/chapter5/README.md @@ -27,12 +27,12 @@ They're all pre-created at the point the program world starts up. This module wi ## `nil` -`nil` quite simply represents _nothing_. It is used to expressly say 'there is an absence of anything here'. +`nil` is an object that simply represents _nothing_. It can't do very much. It is used to expressly say 'there is an absence of anything here'. > If you come from a language that uses `void`, `nil` is Ruby's equivalent to the `void` return type. - _**Figure out similarities and differences between `nil`, `0`, and `false` by playing with `nil` in IRB.**_ -- _**Use the following example to figure out: is `nil` considered true, or false? How about `0`?**_ +- _**Use the following code example to figure out: is `nil` considered true, or false? How about `0`?**_ ```eval-ruby if false @@ -50,17 +50,17 @@ end Other than `true`, `false`, `nil`, and numbers (which, if you read the extension material, you'll know don't _really_ exist as objects until you ask for them by name), there aren't many immediately-useful objects around in a Ruby program by default. Your main job as a programmer is to make new objects to perform certain tasks. -What sort of certain tasks? Let's pick a basic task from our Todo app: +What sort of certain tasks? Let's pick a basic task from a Todo app: ```sh $> todo add put the dog in the car Todo added: Put the dog in the car ``` -In other words, +Broken down into **requirements**, the feature above requires: -- We want the user to be able to input text (a 'todo'), and retrieve it later. -- We might also want to manipulate that text: perhaps, to capitalize it. +- The user to be able to input text (a 'todo'), and retrieve it later. +- The program to manipulate that text: perhaps, to capitalize it. We can't achieve the above just using numbers (well, not easily). We're going to need an object that stores and understands _text_, too. In a moment, we'll meet that object: the string. @@ -74,15 +74,13 @@ In the program world, we call these gods **Classes**. They created all the objec Just like in myths from Ancient Greece, these 'gods' exist as objects inside the universe, and we can send them messages. Mostly, we send them messages asking them to create new objects in the program world. -> Objects created by these classes are called **Instances** of that class. +One such 'god-like' object is the `Integer` class. Integers, like 1, 2, and 5, are called **instances** of the `Integer` class. -One such 'god-like' object is the `Integer` class. Integers, like 1, 2, and 5, are 'instances of the Integer class'. - -When we start `irb` and the integers come into existence, they are created by this particular god. How does that happen? +When we start `irb` and the integer objects come into existence, they are created by sending messages to the `Integer` class. How does that happen? > The true answer is complex, but for now we'll simplify. -When we want to ask a Ruby class to create an instance of itself, we send it the message `new`. So it's reasonable to imagine that, when the program world starts, Ruby automatically does this sort of thing to create the pre-existing integers 'behind the scenes': +When we want to ask a Ruby class to create an instance of itself, we send it the message `new`. So we could imagine that, when the program world starts, Ruby automatically (and secretly) does this sort of thing: ```ruby 1 = Integer.new @@ -104,7 +102,7 @@ We, too, can send the message `new` to these classes. This is how we create new > The process of asking classes to create instances is called **instantiation**. -Let's imagine that we wanted to populate our world with people. Each person has a height. Let's also imagine that Ruby pre-defined a `Person` class. Here's how we'd instantiate people: each time, asking the god of `Person`s to create a new instance with a certain height: +Let's imagine that we wanted to populate our program world with person objects. Each person object has a height. Let's also imagine that Ruby pre-defined a `Person` class. Here's how we'd instantiate person objects: for each person object we want, we ask the god of `Person`s to create a new instance by calling `Person`'s `new` method with the height (an integer object) as an argument: ```irb > Person.new(120) @@ -113,9 +111,9 @@ Let's imagine that we wanted to populate our world with people. Each person has => # ``` -> Notice how we pass the person's height to `Person` along with the `new` message. We'll find out more about how this works later. +#### What's the return value from `new`? -So what's with this `#` thing? Simply: when we tell a class to create a new instance, Ruby gives us the instance we just created: +What's with this `#` thing returned by `Person.new`? Simply: when we tell a class, like `Person` to create a new instance – a new person object – that class returns to us the instance it just created: > @@ -125,14 +123,14 @@ So what's with this `#` thing? Simply: when we tell a c ## Introducing Strings -We want a class whose instances understand working with text. This 'god of text' is provided by Ruby: the `String` class. The `String` class creates instances which: +The 'god of text' object is pre-defined by Ruby: the `String` class. The `String` class creates instances which: * Know about some text we give it, and * Know how to interact with other instances of the `String` class. > We call instances of the `String` class 'strings'. -We need an object that stores and can manipulate some text. What better object than an instance of `String`? +In our Todo example above, we need an object that stores and can manipulate some text. What better object than an instance of `String`? * _**Assign a variable `todo_1` to a new instance of `String`, storing the text `"wash the dog"`.**_ * _**Send the string referenced by `todo_1` a message that activates the `capitalize` method on its interface.**_ @@ -151,13 +149,13 @@ todo_1.capitalize ## Different objects, different purposes -When programming, we often stop to think: "what's the right class to create instances for this particular job?" +When programming, we often stop to think: "what's the right class to create objects to do this particular job?" -To that end, Ruby provides you a wide range of different classes, with different purposes: like `Integer` and `String`. +Ruby provides us a wide range of different classes, each of which create instances with different purposes: like `Integer` and `String`. -Objects created by these classes have quite different purposes. `Integer` instances understand the value of (whole) numbers, and `String` instances understand text. `Integer` instances (integers) know how to interact with other integers, and `String` instances (strings) know how to interact with other strings. +`Integer` instances understand the value of (whole) numbers, and `String` instances understand text. `Integer` instances (integers) know how to interact with other integers, and `String` instances (strings) know how to interact with other strings. -Even though they're different kinds of objects used for different purposes, these objects sometimes respond to the same message – but their methods might do quite different things with those messages. +Even though they're different kinds of objects used for different purposes, strings and integers – and other sorts of objects – sometimes respond to the same message. However, their methods might do quite different things with those messages. What happens if integers try to interact with strings? In other words, what happens if instances of one class try to interact with instances of another? @@ -178,44 +176,53 @@ one_string + two_string %/accordion% -> As you can see, messages are interpreted differently depending on the kind of object they were sent to. - ## Class and Instance interfaces -It's easy to get confused between classes and instances, because they're both objects and they both exist in the program world. Both classes and instances have memory addresses: so their difference in purpose is purely a decision taken by Ruby designers. +You might be forgiven for getting confused between classes and instances. What are some similarities between classes and instances? -> Class objects produce Instance objects: they are not the same object. A god of Fire is not the same object as a lit match. A god of Water is not the same object as a puddle. +- They're both objects. +- They both exist in the program world. +- They both have memory addresses (which is required for both of the above – both have to exist _somewhere_). -Because Classes are different objects to Instances, they have different interfaces. For example: `String` defines `new` on its interface. When called, `String.new` produces a new instance of `String`. String instances, however, do not define `new` on their interfaces, because they're not classes – and so they're not responsible for creating new instances. +But classes and instances are not the same thing: they're designed for difference purposes: -```eval-ruby -string_instance = String.new("some words") -string_instance_instance = string_instance.new -``` +- Class objects produce Instance objects. +- Instance objects do all sorts of things. -Decomposing that error: +> A god of Fire is not the same object as a lit match. A god of Water is not the same object as a puddle. - +Because classes are different objects to instances, they have different interfaces. -And visually depicting what we're trying to do: +> Remember that in Ruby, we mostly define what an object is by what messages it responds to: that is, what its interface is. - +For example: `String` defines `new` on its interface. When called, `String.new` produces a new instance of `String`. String instances, however, do not define `new` on their interfaces, because they're not classes – and so they're not responsible for creating new instances. -> The methods defined on a class interface are called the `class methods`. The methods defined on an instance interface are called the `instance methods`. +```eval-ruby +string_instance = String.new("some words") +string_instance_instance = string_instance.new +``` -> `String`'s _class methods_ include `new`. `String`'s _instance methods_ include `capitalize` and `+`. +> The methods defined on a class interface are called the `class methods`. The methods defined on an instance interface are called the `instance methods`. `String`'s _class methods_ include `new`. `String`'s _instance methods_ include `capitalize` and `+`. ## The main object -Remember that the entire program world is contained within a Universe. That Universe is the Main Object. +Remember that the entire program world is contained within a Universe. That Universe is the main object. If we don't designate an object to send a message to, the message gets sent to the program object: + +```eval-ruby +# Send the message + to integer object 1, with argument 2 +1.+(2) + +# Send the message + to the main object, with argument 2 ++(2) +``` -The Main Object is a special kind of instance: an instance of the `Object` class. When we call methods in IRB without addressing a particular object, we're calling them on the Main Object. +The main object is a special kind of instance: an instance of the `Object` class (yep, there's a class called `Object`). All instances of all classes are, ultimately, instances of the `Object` class. - _**Use the `methods` message in IRB to list the methods on the main object.**_ - _**Send the main object the message `quit`.**_ - _**Explain what just happened.**_ -> Ultimately, all classes are themselves instances of the `Object` class. So if a monotheistic version of the program universe appeals to you, you could think of `Object` as being the 'one true' god entity in Ruby. It's not very specialised – so it doesn't create objects that are very useful for us. +> If a monotheistic version of the program universe appeals to you, you could think of `Object` as being the 'one true' god entity in Ruby. On its own, `Object.new` creates very unspecialized instances with no particular purpose. I often use `Object.new` as a 'placeholder object' if I'm programming and haven't decided which class I'll use to solve a particular problem yet. ## Input and Output @@ -238,47 +245,21 @@ puts String.new("Hello World!") Here's my explanation: the REPL treats you, the REPL-typer, as if you (the programmer) were _just another object in the program_. When you ask an object something – say `1.integer?`, `1` treats that message as if it arrived from another program object. So, you get the return value (`true`), which is helpfully shown to you in the REPL. -On the other hand: the method `puts` doesn't expect to be used from a REPL. It's designed to output information from the program to the console when you're _not_ using the REPL – in other words, when you're not 'inside' the program. `puts` actually does two things: outputs information to the console, and then returns `nil`. +On the other hand: the method `puts` doesn't expect to be used from a REPL. It's designed to output information from the program to the console when you're _not_ using the REPL – in other words, when you're not 'inside' the program. `puts` actually does two things: outputs information to the console, and then returns `nil` to whichever object called it. Which, if you're pretending to be a program object by using the REPL, could be you. %/accordion% -> `puts` is used a lot in pre-written programs, because it's one of the only ways to 'see into' the program world when you're not using a REPL. +> `puts` is used a lot in non-REPL programs, because it's one of the only ways to 'see into' the program world when you're not using a REPL. #### Input -How, then, do we _input_ to a program? Again, there are two ways: +How, then, do we - as programmers – interactively _input_ information to a program? Again, there are two ways: -1. Use the REPL to instruct the program, or objects in the program, directly. +1. Use the REPL to instruct objects in the program directly. 2. Use `gets` to input a string from 'outside the program'. - _**Investigate the difference between the two. Keep in mind the idea of being 'just another object in the program' when you're using the REPL.**_ -`gets` (which stands for 'get string') is very useful to add information on-the-fly to a program: the sort of thing we might need to do, say, if we were writing a build-your-own text adventure game. Which is totally what we're doing in the next module. - -> `gets` calls a method on the main object. This method returns a string. This string usually has a `\n` newline character in it. To get the string without the newline, use the string `chomp` method. A line of code using `gets` usually looks something like this: `user_input = gets.chomp`. - - -## Putting it all together - -Let's build out the first part of our Todo app. - -- _**Start up IRB.**_ -- _**Define variables for three new instances of the `String` class: `todo_1`, `todo_2`, and `todo_3`. Each one should store some text with a todo item.**_ -- _**Create a pre-written world containing `todo_1`, `todo_2`, and `todo_3`, then print them immediately.**_ - -You should be able to run: - -```bash -$> irb -r ./todo.rb -``` - -And immediately see: - -```irb -This is todo item 1 -This is todo item 2 -This is todo item 3 -> -``` +`gets` (which stands for 'get string') is very useful to add information to a program on-the-fly: the sort of thing we might need to do, say, if we were writing a build-your-own text adventure game. Which is totally what we're doing in the next Chapter. -- _**Make it so the user can add a fourth todo item of their choice. This program should run outside of the REPL.**_ +> The message `gets` calls a method on the main object. This method returns a string. This string usually has a `\n` newline character in it. To get the string without the newline, use the string `chomp` method. A line of code using `gets` usually looks something like this: `user_input = gets.chomp`. The program will wait for the user to input a string, then continue with whatever procedure it's doing. diff --git a/chapter6/README.md b/chapter6/README.md index 1e660be..c04e853 100644 --- a/chapter6/README.md +++ b/chapter6/README.md @@ -38,7 +38,7 @@ First up, I'll make a file: `greetings.rb`, and open it in a text editor (like S - The user sees a greeting, which asks them to enter their name. -This one will require `puts`. Inside my `greetings.rb` file, I'll add: +This one will require `puts`. Inside my `greetings.rb` file, I'll instruct the main object to print "Welcome user": ```ruby puts String.new("Welcome user") @@ -46,16 +46,26 @@ puts String.new("Welcome user") #### 3. Get the user's name -This one will require `gets`, and the string method `chomp`. Also, I'll need a variable to store the user's name: +This one will require `gets`, and the string method `chomp`. I'll: + +- Instruct the main object 'get a string' with `gets`, +- Tell the string to remove any `\n` characters by sending it the message `chomp`, +- Store the return value from the string in a variable called `user`. + +I can give these instructions in one line: `user = gets.chomp`. ```ruby puts String.new("Welcome user") user = gets.chomp ``` +> Read through this line I just added methodically. Make sure you understand which object is doing what. + #### 4. Do the simplest thing first (print 'Hi ' plus the name) -Even though the next requirement is to 'shout the user name if it begins with an S', I'm going to make the simplest thing first. That's generally a good strategy, as it avoids getting snarled up early on: +Even though the next requirement is to 'shout the user name if it begins with an S', I'm going to make the simplest thing first. That's generally a good strategy, as it avoids getting snarled up in difficult things early on, allowing me to make progress. + +I'll instruct the main object to print a new instance of String with text "Hi, ", concatenated with the string the main object just assigned to the variable `user`: ```ruby puts String.new("Welcome user") @@ -64,6 +74,8 @@ user = gets.chomp puts String.new("Hi, ") + user ``` +> _concatenated_ is a fancy way for saying 'stuck together with'. + #### 5. Do the hard thing next (work out the conditional) Conditionals are tricky, so I've saved it for nearly-last: @@ -79,9 +91,11 @@ else end ``` +> `user.chr` gets the first character of the string referenced by `user`. I'm checking to see if it's equal to a string containing the text "S". If it is, instruct the main object to print the user. If not, instruct the main object to print a new instance of String with text "Hi, ", concatenated with the string referenced by the variable `user`. + #### 6. Make sure you meet the requirements -Everything's in place with the conditional, except I'm currently printing the `user` without upcasing it. So, let's fix that: +Everything's in place with the conditional, except the main object is currently printing the `user` string without asking it to upcase itself first. So, let's call the `user` string's `upcase` method before printing it: ```ruby puts String.new("Welcome user") @@ -98,7 +112,7 @@ end The last thing I'll do is _refactor_: tidy up my code so it's as good as it can be. -> Your code will be judged according to how readable and consistent it is. Refactoring is a huge field that we'll focus on a lot during the course. It's worth building the basics in here. +> Your code will be judged according to how readable and consistent it is. Refactoring is a key skill that we'll focus on a lot during the course. It's worth building the basics here! I'm going to use several pieces of syntactic sugar to tidy up my code, so it's more readable. The first is to use Ruby's shorthand for creating strings: `"hello"` is syntactic sugar for `String.new("hello")`. @@ -113,7 +127,7 @@ else end ``` -The second refactor I'm going to make is to use **early returns** along with one-line conditionals. Remember that `return` will break a procedure and return whatever value you pass to it. Using this knowledge, along with some syntactic sugar, we can do this: +The second refactor I'm going to make is to use **early returns** along with one-line conditionals. `return` will stop an object from executing a procedure, and return whatever value you tell it to. Using this knowledge, along with some syntactic sugar, we can do this: ```ruby puts "Welcome user" @@ -135,7 +149,7 @@ return puts user.upcase if user.chr == "S" return puts "Hi, #{user}" ``` -> `#{}` inside a string will allow you to execute Ruby inside a string. +> `#{}` inside a string will allow you to execute a statement inside a string. The return value from the statement is concatenated into the surrounding string. We could get this program down to three lines if we wanted to, by using a [ternary operator](https://launchschool.com/books/ruby/read/flow_control). However, I don't think that makes it much better. This code is attractive to me because each line neatly mirrors each requirement we were given. The first line is the first requirement. The second line the second, and so on. @@ -209,7 +223,7 @@ My first requirement (yours might be different) was: 1. The user is told they're facing forward and can type 'forward', 'left', or 'right'. -This we can do with a `puts`: +This we can do by instructing the main object to `puts`: ```ruby puts "You're facing forward. You can type forward, left, or right." @@ -219,7 +233,7 @@ puts "You're facing forward. You can type forward, left, or right." #### 3. Get the user's input -I'm not going to write a loop yet: they're complicated, and I like to save up the complicated stuff til last. Let's just get the user input. This we can do with a `gets` and a `chomp`: +I'm not going to write a loop yet: they're complicated, and I like to save up the complicated stuff til last. Let's just get the user input. This we can do by telling the main object to `gets` a string, telling the string to `chomp`, and assigning the return value to a variable, `user_input`: ```ruby puts "You're facing forward. You can type forward, left, or right." @@ -231,7 +245,7 @@ user_input = gets.chomp 2. If the user enters 'right', they die (goblin). -The specification didn't say what should happen when a user dies (and we can't ask the client what they meant right now), so I'm figuring we just print something, then let the program exit (lazy programmer, right?). +The specification didn't say what should happen when a user dies (and we can't ask the client what they meant right now), so I'm figuring we just get the main object to print something, then let the program exit (lazy programmer, right?). ```ruby puts "You're facing forward. You can type forward, left, or right." @@ -247,7 +261,7 @@ end 3. If the user enters 'left', they die (werewolf). -This is a matter of adding an `elsif` to the conditional from before: +This is a matter of adding another branch to the procedure, by adding an `elsif` to the conditional from before: ```ruby puts "You're facing forward. You can type forward, left, or right." diff --git a/chapter9/README.md b/chapter9/README.md index 2f0826a..1a157a7 100644 --- a/chapter9/README.md +++ b/chapter9/README.md @@ -2,7 +2,9 @@ We've written a lot of procedures, now. They've used conditional logic, loops, iterators, and a number of kinds of specialised object. -But sometimes, we might want to reuse these procedures. Let's say that our program wants to average some test scores for classes, then average the averages of those scores to get an average for the whole school: +But sometimes, we might want to reuse these procedures. Let's say that we have the following program specification: + +> We're a school. Our students have just finished taking their final test. We have test scores for each class of students, and we want to know the average for each class. We also want to know the average for the whole school. ```eval-ruby # Here are the scores @@ -36,9 +38,9 @@ class_2_average = class_2_overall_score_accumulator.to_f / test_scores_for_class class_3_average = class_3_overall_score_accumulator.to_f / test_scores_for_class_3.length # Print out the averages -puts "Class 1 average: " + class_1_average -puts "Class 2 average: " + class_2_average -puts "Class 3 average: " + class_3_average +puts "Class 1 average: " + class_1_average.to_s +puts "Class 2 average: " + class_2_average.to_s +puts "Class 3 average: " + class_3_average.to_s # Store the averages in a variable average_scores_for_classes = [class_1_average, class_2_average, class_3_average] @@ -55,7 +57,7 @@ end school_average = school_overall_score_accumulator.to_f / average_scores_for_classes.length # Print out the school average -puts "School average: " + school_average +puts "School average: " + school_average.to_s # And return it to the REPL school_average @@ -67,12 +69,16 @@ Isn't there a better way? ## Defining methods -Of course is there is. In the example above, we can extract some rules: +Of course is there is. In the example above, we can use algorithmic thinking to extract some rules: 1. Accumulate the scores. 2. Divide the accumulation by the number of scores. -And we can define a procedure that will handle it. To store this procedure, we'll need to define a method: +We can write a procedure that will carry out these rules. And, using a method, we could give that procedure a name. That's what a method is: a **named procedure**. + +> Variables are named objects. Methods are named procedures. + +Here's how we define a method: ```eval-ruby def method_name @@ -90,8 +96,44 @@ end hello_world ``` +So, we're going to define a method called `average`: + +```eval-ruby +def average +end +``` + > Right now, we're defining the method on the program object. That's why we can call our method without referencing an object. More on this in [Chapter 10](../chapter10/README.md) – if you're confused for now, go remind yourself about [Chapter 3](../chapter3/README.md)! +## Return values from methods + +Remember that when we send an object a message, the object invokes a method using that message. Then, it carries out a procedure. Finally, it returns something back to whoever sent the original message. + +To designate what the return value should be from a method, use the `return` keyword: + +```eval-ruby +def gimme_five + # We want the return value to be 5 + return 5 +end + +gimme_five +``` + +Remember that procedures are read by the computer _instruction-by-instruction_. When the computer hits a `return` statement, it'll stop executing the procedure inside the method, and just return whatever it sees. + +That means that any instructions after a `return` instruction won't be executed: + +```eval-ruby +def stop_halfway + return "Stop here" + sum = 1 + 1 + return sum +end +``` + +> Play around with the example above. How can we return `sum` instead? + ## Method parameters Methods can take parameters, which are objects used in the procedure: just like the `each` loop we saw in Chapters [7](../chapter7/README.md) and [8](../chapter8/README.md): @@ -104,7 +146,27 @@ end hello("Sam") ``` -> Just like with the `each` loop, it doesn't matter what we call the parameter. I've called it `person` because that's a clear name for future programmers to read. But `name` would work, as would `nickname`, or `individual`, or `chicken`. So long as we refer to the parameter name we defined in the method procedure, we're golden. +Method parameters define a variable inside the method procedure. The name of the variable is set to the parameter name, and the value is set to whatever argument you pass to the method when you execute it: + +```eval-ruby +def hello(person) + # In a couple of lines' time, 'person' will equal "Sam" + return "Hello, " + person + "!" +end + +hello("Sam") +``` + +Just like with the `each` loop from Chapters [7](../chapter7/README.md) and [8](../chapter8/README.md), it doesn't matter what we call the parameter, so long as we refer to the parameter name in the method procedure. I've called this parameter `person` because that's a clear name for the procedure to use. But `name` would work, as would `nickname`, or `individual`, or `chicken`: + +```eval-ruby +def hello(chicken) + # In a couple of lines' time, 'chicken' will equal "Sam" + return "Hello, " + chicken + "!" +end + +hello("Sam") +``` ## Writing a method procedure @@ -126,9 +188,9 @@ def average(scores) end ``` -> Remember – it doesn't matter what we call the parameter. I've called it `scores` because that's what we're going to pass to average. But `numbers` would work, as would `test_scores`, or `digits`, or `chickens`. So long as we refer to the parameter name we defined in the method procedure, we're good-to-go. +> Remember – it doesn't matter what we call the parameter. The method will assign a variable inside itself, called `scores`, equal to whatever we pass into it. I've called the parameter `scores` because that's what we're going to pass to average. But `numbers` would work, as would `test_scores`, or `digits`, or `chickens`. So long as we refer to the parameter name we defined in the method procedure, we're good-to-go. -From our code before, we know the first thing we do is accumulate the scores together: +From the averaging code we wrote earlier, we know the first thing we do is accumulate the scores together: ```eval-ruby def average(scores) @@ -170,70 +232,11 @@ end average([15, 23, 16, 100, 19]) ``` -OK, let's use `average` to refactor our first code. +What's the benefit of this? Well – now we can use `average` to refactor our first solution to the test scores problem. ## Refactoring code using methods -Let's start with the code we had to begin with: - -```eval-ruby -# Here are the scores -test_scores_for_class_1 = [55, 78, 67, 92] -test_scores_for_class_2 = [48, 99, 91, 70] -test_scores_for_class_3 = [56, 58, 61, 98, 100] - -# Set up the accumulators -class_1_overall_score_accumulator = 0 -class_2_overall_score_accumulator = 0 -class_3_overall_score_accumulator = 0 - -# Add together for class 1 -test_scores_for_class_1.each do |score| - class_1_overall_score_accumulator += score -end - -# Add together for class 2 -test_scores_for_class_2.each do |score| - class_2_overall_score_accumulator += score -end - -# Add together for class 3 -test_scores_for_class_3.each do |score| - class_3_overall_score_accumulator += score -end - -# Get the average for each class by dividing the sum by the number of scores -class_1_average = class_1_overall_score_accumulator.to_f / test_scores_for_class_1.length -class_2_average = class_2_overall_score_accumulator.to_f / test_scores_for_class_2.length -class_3_average = class_3_overall_score_accumulator.to_f / test_scores_for_class_3.length - -# Print out the averages -puts "Class 1 average: " + class_1_average -puts "Class 2 average: " + class_2_average -puts "Class 3 average: " + class_3_average - -# Store the averages in a variable -average_scores_for_classes = [class_1_average, class_2_average, class_3_average] - -# Now set up an accumulator for the school scores -school_overall_score_accumulator = 0 - -# Add together for the school -average_scores_for_classes.each do |class_average| - school_overall_score_accumulator += class_average -end - -# Get the average for the whole school by dividing the sum by the number of classes -school_average = school_overall_score_accumulator.to_f / average_scores_for_classes.length - -# Print out the school average -puts "School average: " + school_average - -# And return it to the REPL -school_average -``` - -First up, let's define and liberally apply our `average` method to it: +First up, let's define and liberally apply our `average` method to our earlier code: ```eval-ruby # Define the average method @@ -258,9 +261,9 @@ class_2_average = average(test_scores_for_class_2) class_3_average = average(test_scores_for_class_3) # Print out the averages -puts "Class 1 average: " + class_1_average -puts "Class 2 average: " + class_2_average -puts "Class 3 average: " + class_3_average +puts "Class 1 average: " + class_1_average.to_s +puts "Class 2 average: " + class_2_average.to_s +puts "Class 3 average: " + class_3_average.to_s # Store the averages in a variable average_scores_for_classes = [class_1_average, class_2_average, class_3_average] @@ -269,7 +272,7 @@ average_scores_for_classes = [class_1_average, class_2_average, class_3_average] school_average = average(average_scores_for_classes) # Print out the school average -puts "School average: " + school_average +puts "School average: " + school_average.to_s # And return it to the REPL school_average @@ -295,12 +298,12 @@ test_scores_for_class_2 = [48, 99, 91, 70] test_scores_for_class_3 = [56, 58, 61, 98, 100] # Print out the averages -puts "Class 1 average: " + average(test_scores_for_class_1) -puts "Class 2 average: " + average(test_scores_for_class_2) -puts "Class 3 average: " + average(test_scores_for_class_3) +puts "Class 1 average: " + average(test_scores_for_class_1).to_s +puts "Class 2 average: " + average(test_scores_for_class_2).to_s +puts "Class 3 average: " + average(test_scores_for_class_3).to_s # Print out the school average -puts "School average: " + average([average(test_scores_for_class_1), average(test_scores_for_class_2), average(test_scores_for_class_3)]) +puts "School average: " + average([average(test_scores_for_class_1), average(test_scores_for_class_2), average(test_scores_for_class_3)]).to_s # And return it to the REPL average([average(test_scores_for_class_1), average(test_scores_for_class_2), average(test_scores_for_class_3)]) @@ -319,7 +322,7 @@ def average(scores) scores_accumulator += score end - puts scores_accumulator.to_f / scores.length + puts (scores_accumulator.to_f / scores.length).to_s scores_accumulator.to_f / scores.length end @@ -358,4 +361,4 @@ If you can answer 'yes' to both 1 and 2, your method is more likely to be a good - `nil` in method bodies -- `return`? \ No newline at end of file +- `return \ No newline at end of file