From 37907c779a124901299022de0cc5606e9b8811dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phaneDucasse?= Date: Sat, 18 Mar 2023 21:18:35 +0100 Subject: [PATCH] migrated to microdown. Now the project should be fully be redone because I have latex errors I do not get --- .../BeaconAndSatellite/BeaconAndSatellite.md | 49 ++ Chapters/Captcha/Captcha.md | 84 +++ Chapters/Converter/Converter.md | 124 ++++ Chapters/Converter/Converter2.md | 190 ++++++ Chapters/Converter/ConverterSolution.md | 7 + Chapters/Counter/Counter.md | 49 ++ Chapters/DSL/DSL.md | 81 +++ Chapters/DSL/DSLSolution.md | 40 ++ Chapters/DoubleDispatch/DoubleDispatch.md | 87 +++ .../DoubleDispatch/DoubleDispatchExercise.md | 29 + Chapters/ESRIASCII/FileOut_emi.md | 565 ++++++++++++++++++ Chapters/ESRIASCII/esriascii.md | 26 + Chapters/Expressions/Expressions.md | 233 ++++++++ Chapters/Expressions/ExpressionsSolution.md | 21 + Chapters/GameCollector/GameCollector.md | 1 + .../GettingStarted/ChallengingYourself.md | 133 +++++ .../ChallengingYourselfSolution.md | 140 +++++ Chapters/GettingStarted/GettingStarted.md | 452 ++++++++++++++ Chapters/GettingStarted/SyntaxSummary.md | 251 ++++++++ Chapters/Inheritance/Extending.md | 60 ++ Chapters/Inheritance/Inheritance.md | 57 ++ Chapters/Inheritance/Inheritance.tex | 329 ---------- Chapters/Introduction/Introduction.md | 2 + .../{JoeTheBox.pillar => JoeTheBox.txt} | 18 +- Chapters/Katas/GramKatas.md | 143 +++++ Chapters/Katas/GramKatasSolution.md | 55 ++ Chapters/Katas/GramVariation.md | 85 +++ Chapters/MessageSending/MessageSending.md | 46 ++ Chapters/OOPNutshell/OOPNutshell.md | 155 +++++ .../ObjectsAndClasses/ObjectsAndClasses.md | 0 .../PaperStoneScissor/PaperStoneScissor.md | 44 ++ .../PlayingWithTurtles/PlayingWithTurtles.md | 1 + Chapters/QRCode/QRCode.md | 1 + Chapters/SimpleLAN/SimpleLAN.md | 315 ++++++++++ Chapters/SimpleLAN/SimpleLANSolution.md | 77 +++ Chapters/SnakesAndLadders/SnakesAndLadders.md | 411 +++++++++++++ .../SnakesAndLaddersSolution.md | 66 ++ Chapters/SyntaxGlimpse/SyntaxGlimpse.md | 83 +++ Chapters/SyntaxNutshell/SyntaxNutshell.md | 68 +++ Chapters/Tamagoshi/Tamagoshi.md | 223 +++++++ Chapters/Tests/Tests.md | 68 +++ Chapters/TinyChat/TinyChat.md | 187 ++++++ Chapters/Visitor/Visitor.md | 208 +++++++ Chapters/Visitor/VisitorSolution.md | 21 + Chapters/Wallet/Wallet.md | 197 ++++++ Chapters/Wallet/WalletSolution.md | 46 ++ index.md | 1 + index.pillar | 73 --- pillar.conf | 17 +- 49 files changed, 5185 insertions(+), 434 deletions(-) create mode 100644 Chapters/BeaconAndSatellite/BeaconAndSatellite.md create mode 100644 Chapters/Captcha/Captcha.md create mode 100644 Chapters/Converter/Converter.md create mode 100644 Chapters/Converter/Converter2.md create mode 100644 Chapters/Converter/ConverterSolution.md create mode 100644 Chapters/Counter/Counter.md create mode 100644 Chapters/DSL/DSL.md create mode 100644 Chapters/DSL/DSLSolution.md create mode 100644 Chapters/DoubleDispatch/DoubleDispatch.md create mode 100644 Chapters/DoubleDispatch/DoubleDispatchExercise.md create mode 100644 Chapters/ESRIASCII/FileOut_emi.md create mode 100644 Chapters/ESRIASCII/esriascii.md create mode 100644 Chapters/Expressions/Expressions.md create mode 100644 Chapters/Expressions/ExpressionsSolution.md create mode 100644 Chapters/GameCollector/GameCollector.md create mode 100644 Chapters/GettingStarted/ChallengingYourself.md create mode 100644 Chapters/GettingStarted/ChallengingYourselfSolution.md create mode 100644 Chapters/GettingStarted/GettingStarted.md create mode 100644 Chapters/GettingStarted/SyntaxSummary.md create mode 100644 Chapters/Inheritance/Extending.md create mode 100644 Chapters/Inheritance/Inheritance.md delete mode 100755 Chapters/Inheritance/Inheritance.tex create mode 100644 Chapters/Introduction/Introduction.md rename Chapters/JoeTheBox/{JoeTheBox.pillar => JoeTheBox.txt} (98%) create mode 100644 Chapters/Katas/GramKatas.md create mode 100644 Chapters/Katas/GramKatasSolution.md create mode 100644 Chapters/Katas/GramVariation.md create mode 100644 Chapters/MessageSending/MessageSending.md create mode 100644 Chapters/OOPNutshell/OOPNutshell.md create mode 100644 Chapters/ObjectsAndClasses/ObjectsAndClasses.md create mode 100644 Chapters/PaperStoneScissor/PaperStoneScissor.md create mode 100644 Chapters/PlayingWithTurtles/PlayingWithTurtles.md create mode 100644 Chapters/QRCode/QRCode.md create mode 100644 Chapters/SimpleLAN/SimpleLAN.md create mode 100644 Chapters/SimpleLAN/SimpleLANSolution.md create mode 100644 Chapters/SnakesAndLadders/SnakesAndLadders.md create mode 100644 Chapters/SnakesAndLadders/SnakesAndLaddersSolution.md create mode 100644 Chapters/SyntaxGlimpse/SyntaxGlimpse.md create mode 100644 Chapters/SyntaxNutshell/SyntaxNutshell.md create mode 100644 Chapters/Tamagoshi/Tamagoshi.md create mode 100644 Chapters/Tests/Tests.md create mode 100644 Chapters/TinyChat/TinyChat.md create mode 100644 Chapters/Visitor/Visitor.md create mode 100644 Chapters/Visitor/VisitorSolution.md create mode 100644 Chapters/Wallet/Wallet.md create mode 100644 Chapters/Wallet/WalletSolution.md create mode 100644 index.md delete mode 100644 index.pillar diff --git a/Chapters/BeaconAndSatellite/BeaconAndSatellite.md b/Chapters/BeaconAndSatellite/BeaconAndSatellite.md new file mode 100644 index 0000000..ba55054 --- /dev/null +++ b/Chapters/BeaconAndSatellite/BeaconAndSatellite.md @@ -0,0 +1,49 @@ +## Beacons and Satellites @ch:beacon In this chapter you will build a simulator for beacons and satellites. Beacons are in the sea and collect data and from time to time they should synchronise with satelittes to send data. In reality, satelittes broadcast signals and beacons are polling at regular interval for signals, then once they know that they are in range based on the first signal, a communication is established and data is exchanged. In the simulator we will present how we can implement communication between losely coupled objects. You will build step by step different variations around the observer/observable idiom. This idiom is important since it is used in Model-View-Controller and Self addressed stamped enveloppe \(S.A.S.E\) patterns. Beacons will register to satellites and when the satelittes are in range they will notify the beacons interested in the notification. ### Description ![Beacons and Satelittes.](figures/Beacons.png width=70&label=fig:Beacon) A beacon is inside the sea and it collects data. It is fully autonomous. After a certain period of time it migrates to the surface waiting to send the data it collected. To communicate with satelittes, a satelitte should be available, i.e., within the zone where the beacon is. A satellite is moving around earth at a certain speed and ranging a portion of sea. It can only communicate with beacons within such range. The system is fully dynamic in the sense that new beacons may be added or removed. Satelittes may be present or not. ### A simple model ``` Object subclass: #Satelitte + instanceVariableNames: 'observers' + classVariableNames: '' + package: 'SatelitteAndBeacon' ``` ``` Satelitte >> initialize + observers := OrderedCollection new ``` ``` Object subclass: #Beacon + instanceVariableNames: 'data' + classVariableNames: '' + package: 'SatelitteAndBeacon' ``` ### V1: Simple observer / observable We start with a simple schema where beacons - register to satellites and - when the satelittes are in range they notify the beacons that registered. #### Registration A beacon register to a satellite as follows: ``` Satelitte >> register: aBeacon + self addObserver: aBeacon ``` #### Notification ``` Satelitte >> position: aPoint + position := aPoint. + self notify ``` ``` Satelitte >> notify + observers do: [ :aBeacon | aBeacon salelittePositionChanged: self ] ``` ### V1 Implementation ??? ### V2: Analysis This first implementation has several drawbacks. - One of the problem is that the message is hardcoded. - Second Imagine that the satellite should emit different notification for its position, protocol to be used, frequency.... and each kind of beacon can register for the notification kinds that fits it. We must have a list of each kind of observed property. ### V2: Introducing events ``` Satelitte >> register: aBeacon forEvent: aEventClass + aSatelitte1 addObserver: aBeacon1 with: aEventClass ``` ``` Satelitte >> addObserver: anObserver with: anEventClass + observerDict at: anEventClass iAbsentPut: [OrderedCollection new]. + (observerDict at: anEventClass) add: anObserver ``` ``` Satelitte >> position: aPoint + position := aPoint. + self notify: (PositionChanged with: self) ``` ``` Satelitte >> notify: anEvent + (observersDict at: anEvent class) ifPresent: [ :aBeaconList | + aBeaconList do: [:aBeacon| anEvent fireOn: aBeacon ] ``` #### Implementation ``` Object subclass: #SBEvent + instanceVariableNames: 'observable' + classVariableNames: '' + package: 'SatelitteAndBeacon' ``` ``` SBEvent subclass: #SBPositionChanged + instanceVariableNames: '' + classVariableNames: '' + package: 'SatelitteAndBeacon' ``` ``` SBEvent subclass: #SBProtocolChanged + instanceVariableNames: '' + classVariableNames: '' + package: 'SatelitteAndBeacon' ``` ``` SBPositionChanged >> fireOn: anObserver + anObserver salelittePositionChanged: observable ``` ``` SBProtocolChanged >> fireOn: anObserver + anObserver salelitteProtocolChanged: observable ``` #### V2 analysis Advantages - we reuse the same mechanism for different kind of observable properties. Drawbacks - One event means that the message is also hardcoded. There is tight dependencies between the event type and the kind of behavior that is available on the observer side. ### V3 Specifying the message Now the observer can specify the message that it wants to receive. ``` aSatelitte1 when: SBPositionChanged send: #readyForHandShakeWith: to: aBeacon1 ``` ``` aSatelitte1 when: SBProtocolChanged send: #useProtocol: to: aBeacon1 ``` ``` Satelitte >> when: anEventClass send: aSelector to: anObserver + observerDict at: anEventClass iAbsentPut: [OrderedCollection new]. + (observerDict at: anEventClass) add: (aSelector -> anObserver) ``` ``` Satelitte >> position: aPoint + position := aPoint. + self notify: (PositionChanged with: self) ``` ``` Satelitte >> notify: anEvent + (observersDict at: anEvent class) ifPresent: [ :aBeaconList | + aBeaconList do: [ :aBeaconAssoc | + aBeaconAssoc value perform: aBeaconAssoc key with: anEvent) ] ``` ### V5 Factoring out the announcer The notification and management at notification should be packaged as a separate class so that we can reuse it by just delegating to it. ``` Object subclass: #BSAnnouncement + instanceVariableNames: 'selector observer' ``` ``` Object subclass: #BSAnnouncer + instanceVariableNames: 'observerDict' ``` ``` BSAnnouncer >> when: anEventClass send: aSelector to: anObserver + observerDict at: anEventClass iAbsentPut: [ OrderedCollection new] . + (observerDict at: anEventClass) add: + (BSAnnouncement send: aSelector to: anObserver) ``` ``` BSAnnouncer >> notify: anEvent + (observersDict at: anEvent class) ifPresent: [ :aBeaconList | + aBeaconList do: [ :anAnnouncement | + anAnnouncement observer + perform: anAnnouncement selector + with: anEvent) ] ``` ``` Satelitte >> notify: anEvent + self announcer notify: anEvent ``` ``` Satelitte >> when: anEventClass send: aSelector to: anObserver + self announcer when: anEventClass send: aSelector to: anObserver ``` ### Discussion about lookup of events \ No newline at end of file diff --git a/Chapters/Captcha/Captcha.md b/Chapters/Captcha/Captcha.md new file mode 100644 index 0000000..fd69ebd --- /dev/null +++ b/Chapters/Captcha/Captcha.md @@ -0,0 +1,84 @@ +## Fun with Capchas \(not used for now\) Capchas are ways to control that a service is not accessed by a robot. In this chapter we propose to implement some capchas as a way to practice and learn Pharo. Several capcha will be implemented but first let us start with just manipulating strings. ## Reversing a string The problem that we propose to you is to reserve a string. It can be useful to build capcha as we will show later and it is a small and simple one that we will teach how to interact with strings and with collections of objects. ``` 'frog' +-> +'grof' ``` ### Solution #4 There are several solutions to this problem and we will look at some of them but first let's look at some basic elements about strings. ### Basic on strings A string is a sequence of characters and the first one has the index 1 \(and not zero like in some other languages\) ``` 'frog' size +--> 4 ``` To access an element we use the message `at: anIndex`. ``` 'frog' at: 1 +-> $f ``` To set the value of a string element we use the message `at:anIndex put: aCharacter` ``` | s | +s := 'frog'. +s at: 1 put: $z. +s +-> zrog ``` ### Reversing Now to reverse the string 'frog' we see that the `$f` index 1 should be put in the position 4, the `$r` index 2 in the position 3, the `$o` index 3 in the position 2 and `$g` index 4 should be put in the position 1. We should find a relation between the actual index of the character and its future index once the string will be reversed. Let us think again about it by now using the original string size \(i.e., 4\). It seems that the target index is `size + 1 - source index`. Let us verify this hypothesis: `$f` whose index is 1, will be put in the index `4 + 1 - 1 = 4`, the `$r` whose index is 2, will be put in the position `4 + 1 - 2 = 3`, the `$o` whose index is 3, will be put in the position ` 4 + 1 - 3 = 2` and `$g` index 4 should be put in the position 1. To solve this problem we also need to know how to perform a loop. The following script prints all the number from 1 to 10. ``` 1 to: 10 do: [:i | Transcript show: i printString; cr]. ``` The following script prints each element of the string 'frog' one after the other. ``` 1 + to: 'frog' size + do: [ :i | Transcript show: ('frog' at: i) ; cr ] ``` ``` | sourceString targetString | +sourceString := 'frog'. +targetString := String new: sourceString size. + +1 to: targetString size do: [ :i | + targetString at: (sourceString size + 1) - i put: (sourceString at: i) + ]. +targetString ``` Now we can define a new method on the class `String`. In such case the sourceString variable is not necessarily anymore since this is the string receiving the message that will be the sourceString. ``` String>>captchaReversed + + | targetString | + targetString := String new: self size. + + 1 to: targetString size do: [ :i | + targetString at: (self size + 1) - i put: (self at: i) + ]. + ^ targetString ``` ``` 'youpi' captchaReversed +-> 'ipuoy' ``` Now `self size + 1` is invariant during the loop. It does not change so we can extract it outside the loop. ``` String>>captchaReversed + + | targetString n | + targetString := String new: self size. + n := (self size + 1). + 1 to: targetString size do: [ :i | + targetString at: n - i put: (self at: i) + ]. + ^ targetString + ]]] + +Now looking at how it is implemented in Pharo we see the following definition defined in the superclass of ==String==. + + +[[[ +SequenceableCollection>>reversed + "Answer a copy of the receiver with element order reversed." + "Example: 'frog' reversed" + + | n result src | + n := self size. + result := self species new: n. + src := n + 1. + 1 to: n do: [:i | result at: i put: (self at: (src := src - 1))]. + ^ result ``` `self species new: n.` makes the code working on several different collection. `species` returns a class of the same species than the receiver of the message, the class `Array` if the receiver is an array, the String class if the receiver a string. So result points on a new string or collection. ## Palindroms The following example brings an interesting idea for another string manipulation for captcha: asking the user to enter an palindrome. Palindrome are words or sentences which are symmetrical. For example, 'civic', 'refer' and 'No lemon, no melon' are palidromes. ``` 'civic' captchaReversed +-> +'civic' ``` Here is a way to check if a string is a capcha. ``` captchaIsAnagram + "Returns true whether the receiver is an anagram. + 'anna' captchaIsAnagram + true + 'andna' captchaIsAnagram + true + 'avdna' captchaIsAnagram + false + " + 1 + to: self size//2 + do: [ :i | (self at: i) = (self at: self size + 1 - i) + ifFalse: [ ^false ] + ]. + ^true ``` Now again the expression `self size + 1` is constant. ``` captchaIsAnagram + "Returns true whether the receiver is an anagram. + 'anna' captchaIsAnagram + true + 'andna' captchaIsAnagram + true + 'avdna' captchaIsAnagram + false + " + | n | + n := self size + 1. + 1 + to: self size//2 + do: [ :i | + ( self at: i ) = ( self at: n - i) + ifFalse: [ ^false ] + ]. + ^true ``` % Local Variables: % eval: (flyspell-mode -1) % End: \ No newline at end of file diff --git a/Chapters/Converter/Converter.md b/Chapters/Converter/Converter.md new file mode 100644 index 0000000..35e0a82 --- /dev/null +++ b/Chapters/Converter/Converter.md @@ -0,0 +1,124 @@ +## Converter @cha_converter In this chapter you will implement a little temperature converter between celsius and fahrenheit degrees. It is so simple that it will help us to get started with Pharo and also with test driven development. Near the end of the chapter we will add logging facilities to the converter so that we can log the temperatures of certain locations. For this you will create a simple class and its tests. We will show how to write test to specify the expected results. Writing tests is really important. It is one important tenet of Agile Programming and Test Driven Development \(TDD\). We will explain later why this is really good to have tests. For now we just implement them. We will also discuss a bit a fundamental aspects of float comparison and we will also present some loops. ### First a test First we define a test class named `TemperatureConverterTest` within the package `MyConverter`. It inherits from the class `TestCase`. This class is special, any method starting with `'test'` will be executed automatically, one by one each time on a new instance of the class \(to make sure that tests do not interfere with each others\). ``` TestCase subclass: #TemperatureConverterTest + instanceVariableNames: '' + classVariableNames: '' + package: 'MyConverter' ``` Converting from Fahrenheit to Celsius is done with a simple linear transformation. The formula to get Fahrenheit from Celsius is F = C * 1.8 + 32. Let us write a test covering such transformation. 30 Celsius should be 86 Fahrenheit. ``` testCelsiusToFahrenheit + + | converter | + converter := TemperatureConverter new. + self assert: ((converter convertCelsius: 30) = 86.0) ``` The test is structured the following way: - Its selector starts with `test`, here the method is named `testCelsiusToFahrenheit`. - It creates a new instance of `TemperatureConverter` \(it is called the _context_ of the test or more technically its fixture\). - Then we check using the message `assert:` that the expected behavior is really happening. The message `assert:` expects a boolean. Here the expression `((converter convertCelsius: 30) = 86.0)` returns a boolean. `true` if the converter returns the value 86.0, `false` otherwise. The testing framework also offers some other similar methods. One is particularly interesting: `assert:equals:` makes the error reporting more user friendly. The previous method is strictly equivalent to the following one using `assert:equals:`. ``` testCelsiusToFahrenheit + + | converter | + converter := TemperatureConverter new. + self assert: (converter convertCelsius: 30) equals: 86.0 ``` The message `assert:equals:` expects an expression and a result. Here `(converter convertCelsius: 30)` and `86.0`. You can use the message you prefer and we suggest to use `assert:equals:` since it will help you to understand your mistake by saying: `'You expect 86.0 and I got 30'` instead of simply telling you that the result is `false`. ### Define a test method \(and more\) While defining the method `testCelsiusToFahrenheit` using the class browser, the system will tell you that the class `TemperatureConverter` does not exist \(This is true because we did not define it so far\). The system will propose to create it. Just let the system do it. Once you are done. You should have two classes: `TemperatureConverterTest` and `TemperatureConverter`. As well as one method: `testCelsiusToFahrenheit`. The test does not pass since we did not implement the conversion method \(as shown by the red color in the body of `testCelsiusToFahrenheit`\). % %@@todo add screenshot Note that you entered the method above and the system compiled it. Now in this book we want to make sure that you know about which method we are talking about hence we will prefix the method definitions with their class. For example the method `testCelsiusToFahrenheit` in the class `TemperaturConverterTest` is defined as follows: ``` TemperaturConverterTest >> testCelsiusToFahrenheit + + | converter | + converter := TemperatureConverter new. + self assert: (converter convertCelsius: 30) equals: 86.0 ``` ### The class TemperaturConverter The class `TemperaturConverter` is defined as shown below. You could have define it before defining the class `TemperaturConverterTest` using the class definition below: ``` Object subclass: #TemperatureConverter + instanceVariableNames: '' + classVariableNames: '' + package: 'MyConverter' ``` This definition in essence, says that: - We want to define a new class named `TemperaturConverter`. - It has no instance or class variables \(`''` means empty string\). - It is packaged in package `MyConverter`. Usually when doing Test Driven Development with Pharo, we focus on tests and lets the system propose us some definitions. Then we can define the method as follows. ``` TemperatureConverter >> convertCelsius: anInteger + "Convert anInteger from celsius to fahrenheit" + + ^ ((anInteger * 1.8) + 32) ``` The system may tell you that the method is an utility method since it does not use object state. It is a bit true because the converter is a _really_ simple object. For now do not care. Your test should pass. Click on the icon close to the test method to execute it. ### Converting from Farhenheit to Celsius Now you got the idea. Let us define a test for the conversion from Fahrenheit to Celsius. ``` TemperatureConverterTest >> testFahrenheitToCelsius + + | converter | + converter := TemperatureConverter new. + self assert: (converter convertFarhenheit: 86) equals: 30.0. + self assert: (converter convertFarhenheit: 50) equals: 10 ``` Define the method `convertFarhenheit: anInteger` ``` TemperatureConverter >> convertFarhenheit: anInteger + "Convert anInteger from fahrenheit to celsius" + + ... Your solution ... ``` Run the tests they should all pass. ### About floats The conversions method we wrote returns floats. Floats are special objects in computer science because it is complex to represent infinite information \(such as all the numbers between two consecutive integers\) with a finite space \(numbers are often represented with a fixed number of bits\). In particular we should pay attention when comparing two floats. Here is a surprising case: we add two floats and the sum is not equal to their sums. ``` (0.1 + 0.2) = 0.3 +> false ``` This is because the sum is not just equal to `0.3`. The sum is in fact the number `0.30000000000000004` ``` (0.1 + 0.2) +> 0.30000000000000004 ``` To solve this problem in Pharo \(it is the same in most programming languages\), we do not use equality to compare floats but alternate messages such as `closeTo:` or `closeTo:precision:` as shown below: ``` (0.1 + 0.2) closeTo: 0.3 +> true +(0.1 + 0.2) closeTo: 0.3 precision: 0.001 +> true ``` To know more, you can have a look at the Fun with Float chapter in Deep Into Pharo \([http://books.pharo.org](http://books.pharo.org)\)\). The key point is that in computer science you should always avoid to compare the floats naively. So let us go back to our conversion: ``` ((52 - 32) / 1.8) +> 11.11111111111111 ``` In the following expression we check that the result is close to 11.1 with a precision of 0.1. It means that we accept as result 11 or 11.1 ``` ((52 - 32) / 1.8) closeTo: 11.1 precision: 0.1 +> true ``` We can use `closeTo:precision:` in our tests to make sure that we deal correctly with the float behavior we just described. ``` ((52 - 32) / 1.8) closeTo: 11.1 precision: 0.1 +> true ``` We change our tests to reflect this ``` TemperatureConverterTest >> testFahrenheitToCelsius + + | converter | + converter := TemperatureConverter new. + self assert: ((converter convertFarhenheit: 86) closeTo: 30.0 precision: 0.1). + self assert: ((converter convertFarhenheit: 50) closeTo: 10 precision: 0.1) ``` ### Printing rounded results The following expression shows that we may get converted temperature with a too verbose precision. ``` (TemperatureConverter new convertFarhenheit: 52) +>11.11111111111111 ``` Here just getting 11.1 is enough. There is no need to get the full version. In fact, we can manipulate floats in full precision but there are case like User Interfaces where we would like to get a shorter sort of information. Typically as user of the temperature converter, our body does not see the difference between 12.1 or 12.2 degrees. Pharo libraries include the message `printShowingDecimalPlaces: aNumberOfDigit` used to round the _textual_ representation of a float. ``` (TemperatureConverter new convertFarhenheit: 52) printShowingDecimalPlaces: 1 +>11.1 ``` ### Building a map of degrees Often when you are travelling you would like to have kind of a map of different degrees as follows: Here we want to get the converted values between 50 to 70 fahrenheit degrees. ``` (TemperatureConverter new convertFarhenheitFrom: 50 to: 70 by: 2). +> { 50->10.0. + 52->11.1. + 54->12.2. + 56->13.3. + 58->14.4. + 60->15.6. + 62->16.7. + 64->17.8. + 66->18.9. + 68->20.0. + 70->21.1} ``` What we see is that the method `convertFarhenheitFrom:to:by:` returns an array of pairs. A pair is created using the message `->` and we can access the pair elements using the message `key` and `value` as shown below. ``` | p1 | +p1 := 50 -> 10.0. +p1 key +>>> 50 +p1 value +>>> 10.0 ``` Let us write a test first. We want to generate map containing as key the fahrenheit and as value the converted celsius. Therefore we will get a collection with the map named `results` and a collection of the expected values that the value of the elements should have. On the two last lines of the test method, using the message `with:do:` we iterate on both collections in parallel taking on element of each collection and compare them. ``` TemperatureConverterTest >> testFToCScale + + | converter results expectedCelsius | + converter := TemperatureConverter new. + results := (converter convertFarhenheitFrom: 50 to: 70 by: 2). + expectedCelsius := #(10.0 11.1 12.2 13.3 14.4 15.5 16.6 17.7 18.8 20.0 21.1). + + results with: expectedCelsius + do: [ :res :cel | res value closeTo: cel ] ``` Now we are ready to implement the method `convertFarhenheitFrom: low to: high by: step`. Using the message `to:by:`, we create an interval to generate the collection of numbers starting at low and ending up at high using the increment step. Then we use the message `collect:` which applies a block to a collection and returns a collection containing all the values returned by the block application. Here we just create a pair whose key is the fahrenheit and whose value is its converted celsius value. ``` TemperatureConverter >> convertFarhenheitFrom: low to: high by: step + "Returns a collection of pairs (f, c) for all the fahrenheit temperatures from a low to an high temperature" + + ^ (low to: high by: step) + collect: [ :f | f -> (self convertFarhenheit: f) ] ``` ### Spelling Fahrenheit correctly! You may not noticed but we badly spelled fahrenheit since the beginning of this chapter! Fahrenheit is not spelt farhenheit but Fahrenheit. Now you may start to think that I'm crazy, because you should rename all the methods you wrote and in addition all the users of such methods and after we should check that we did not break anything. And you can think that this is a huge task. Well first you should rename the methods because nobody wants to keep badly named code. Second, I'm not crazy at all. Programmers rename their code regularly because they often do not get it right the first time, or even the second time... Often you rewrite your code after thinking more about the interface you finally understand that you should propose. In fact good designer think a lot about names because names convey the intent of a computation. Now we have two super powerful tools to help us: Refactorings and Tests. We will use the **Rename method** refactoring proposed by Pharo. A refactoring is a code transformation that preserves code properties. The Rename method refactoring garantees that not only the method but all the places where it is called will also be renamed to send the new message. In addition a refactoring garantees that the behavior of the program is not modified. So this is really powerful. Select the method `convertFarhenheit:` in the method list and bring the menu, use the **Rename method \(all\)** item, give a new name `convertFahrenheit:`. The system will prompt you to show you all the corresponding operations. Check them to see what you should have done manually. Imagine the amount of mistakes you could have made and proceed. Do the same for `convertFahrenheitFrom:to:by:`. Now the key question is if these changes broke anything. Normally everything should work since this is what we expect when using refactorings. But runnning the tests has the final word. So run the tests to check if everything is ok and here is a clear use of tests: they ensure that we can spot fast a regression. With this little scenario you should have learned two important things: - Tests are written once and executed million times to check for regression. - Refactorings are really powerful operations that save us from tedious manual rewriting. ### Adding logging behavior Imagine now that you want to monitor the different temperatures between the locations where you live and where you work. \(This is a real scenario since the building where my office is located got its heating broken over winter and I wanted to measure and keep a trace of the different temperatures in both locations.\) Here is a test representing a typical case. First, since I want to distinguish my measurements based on the locations, I added the possibility to specify a location. Then I want to be able to record temperatures either in celsius or in fahrenheit. Since the temperature often depends on the moment during the day I want to log the date and time with each measure. Then we can request a converter for all the dates \(message `dates`\) and temperatures \(message `temperatures`\) that it contains. ``` TemperatureConverterTest >> testLocationAndDate + + | office | + office := TemperatureConverter new location: 'Office'. + "perform two measures that are logged" + office measureCelsius: 19. + office measureCelsius: 21. + + "We got effectively two measures" + self assert: office measureCount = 2. + + "All the measures were done today" + self assert: office dates equals: {Date today . Date today} asOrderedCollection. + + "We logged the correct temperature" + self assert: office temperatures equals: { 19 . 21 } asOrderedCollection ``` The first thing that we do is to add two instance variables to our class: `location` that will hold the name of the location we measure and `measures` a collection that will hold all the temperatures and dates. ``` Object subclass: #TemperatureConverter + instanceVariableNames: 'location measures' + classVariableNames: '' + package: 'MyConverter' ``` We initialize such variable with the following values. ``` TemperatureConverter >> initialize + location := 'Nice'. + measures := OrderedCollection new ``` It means that each instance will be able to have its own location and its own collection of measures. Now we are ready to record a temperature in celsius. What we do is that we add pair with the time and the value to our collection of measures. ``` TemperatureConverter >> measureCelsius: aTemp + measures add: DateAndTime now -> aTemp ``` To support tests we also define a method returning the number of current measure our instance holds. ``` TemperatureConverter >> measureCount + ... Your code ... ``` We now define three methods returning the sequence of temperatures, the dates and the times. Since the time has a microsecond precision it is a bit difficult to test. So we only test the dates. ``` TemperatureConverter >> temperatures + ^ measures collect: [ :each | each value ] ``` To produce time without micro second we suggest to print the time using `print24`. ``` DateAndTime now asTime print24 +>>> '22:46:33' ``` ``` TemperatureConverter >> times + ^ measures collect: [ :each | each key asTime ] ``` ``` TemperatureConverter >> dates + ... Your code ... ``` Now we can get two converters each with its own location and measurement records. The following tests verify that this is the case. ``` TemperatureConverterTest >> testTwoLocations + + | office home | + office := TemperatureConverter new location: 'office'. + office measureFahrenheit: 19. + office measureFahrenheit: 21. + self assert: office location equals: 'office'. + self assert: office measureCount equals: 2. + home := TemperatureConverter new location: 'home'. + home measureFahrenheit: 22. + home measureFahrenheit: 22. + home measureFahrenheit: 22. + self assert: home location equals: 'home'. + self assert: home measureCount equals: 3. ``` We can add now a new method to convert fahrenheit to celcius and we define a new test first. ``` TemperatureConverterTest >> testLocationAndDateWithConversion + + | converter | + converter := TemperatureConverter new location: 'Lille'. + converter measureFahrenheit: 86. + converter measureFahrenheit: 50. + self assert: converter measureCount equals: 2. + self assert: converter dates + equals: {Date today . Date today} asOrderedCollection. + self assert: converter temperatures + equals: { converter convertFahrenheit: 86 . + converter convertFahrenheit: 50 } asOrderedCollection ``` What we do is that since celsius is the scientific unity for temperature we convert to celsius before recording our temperature. ``` TemperatureConverter >> measureFahrenheit: aTemp + ... Your code ... ``` ### Discussion From a design perspective we see that the logger behavior is a much better object than the converter. The logger keeps some internal data specific to a location while the converter is stateless. Object-oriented programming is much better for capturing object with state. This is why the converter was a kind of silly objects but it was to get you started. Now it is rare that the world we want to model and represent is stateless. This is why object-oriented programming is a powerful way to develop complex programs. ### Conclusion In this chapter we built a simple temperature converter. We showed how define and execute unit tests using a Test Driven approach. The interest in testing and Test Driven Development is not limited to Pharo. Automated testing has become a hallmark of the _Agile software development_ movement, and any software developer concerned with improving software quality would do well to adopt it. We showed that tests are an important aid to measure our progress and also are an important aid to define clearly what we want to develop. \ No newline at end of file diff --git a/Chapters/Converter/Converter2.md b/Chapters/Converter/Converter2.md new file mode 100644 index 0000000..2516cf7 --- /dev/null +++ b/Chapters/Converter/Converter2.md @@ -0,0 +1,190 @@ +## Temperature Logger \(under writing\) @cha_converter In this chapter you will implement a little temperature logger. It is really simple that it will help us to get started with Pharo and also with test driven development. We will collect temperate measurements and compute temperature average and display the temperatures in a graph. Near the end of the chapter, we will add the possibility to convert between celcius and fahrenheit. For this you will create a simple class and its tests. We will show how to write test to specify the expected results. Writing tests is really important. It is one important tenet of Agile Programming and Test Driven Development \(TDD\). We will explain later why this is really good to have tests. For now we just implement them. We will also discuss a bit the fundamental aspects of float comparison and we will also present some loops. ### First a test Imagine that you want to monitor the different temperatures between the locations where you live and where you work. \(This is a real scenario since the building where my office is located got its heating broken over winter and I wanted to measure and keep trace of the different temperatures in both locations.\) We will define tests to capture the expected behavior. First we define a test class named `TemperatureLoggerTest` within the package `TemperatureLogger`. It inherits from the class `TestCase`. This class is special, any method starting with `'test'` will be executed automatically, one by one each time on a new instance of the class \(to make sure that tests do not interfere with each others\). ``` TestCase subclass: #TemperatureLoggerTest + instanceVariableNames: '' + classVariableNames: '' + package: 'TemperatureLogger' ``` Here is a test method named `testRecords` representing a typical case. First, since we want to distinguish measurements based on the locations, we added the possibility to specify a location. Then we record temperatures. ``` TemperatureLoggerTest >> testRecords + + | office | + office := TemperatureLogger new location: 'Office'. + "Perform two measures that are logged" + office recordTemperature: 19. + office recordTemperature: 21. + + "We got effectively two measures" + self assert: office recordCount equals: 2. ``` The test is structured the following way: - Its selector starts with `test`, here the method is named `testRecords`. - It creates a new instance of `TemperatureLogger` \(it is called the _context_ of the test or more technically its fixture\). Note that for now the class `TemperatureLogger` is not defined yet but this is not a problem. - Then we check using the message `assert:equals:` that the expected behavior is really happening. Here we test that we effectively recorded two measures. ### Making the test pass The first thing that we do is to define a class named `TemperatureLogger`. We add two instance variables to our class: `location` that will hold the name of the location of our measure and `measures` a collection that will hold all the recorded temperatures. It means that each instance of the class `TemperatureLogger` will be able to have its own location and its own collection of measures. ``` Object subclass: #TemperatureLogger + instanceVariableNames: 'location measures' + classVariableNames: '' + package: 'Converter' ``` We defined the method `initialize` to initialize our logger so that it can be correctly used. Such `initialize` method is executed each time new instance is created. We initialize such instance variables with the following values: we set by default the location to be 'home' and we initialize the `measures` instance variables to hold a collection in which we will add measurements. An ordered collection is a collection of objects that is ordered and can grow over time. ``` TemperatureLogger >> initialize + location := 'Home'. + measures := OrderedCollection new ``` !!note The method `initialize` is executed each time we create a new object using the message `new` \(sent to the class\). We add a method to set the location: ``` TemperatureLogger >> location: aNumber + + location := aNumber ``` Now we are ready to record a temperature in celsius. ### Record temperature What we do is to add a measure to our collection of measures. ``` TemperatureLogger >> recordTemperature: aNumber + "Add the temperature to the list of recorded measures" + + measures add: aNumber ``` Then you should define the method `recordCount` which returns the number of measures done so far. Its implementation is to return the size of the collection holding the recorded temperatures. ``` TemperatureLogger >> recordCount + + ... ``` Our test should now pass. We suggest you save your code. ### Logger recording improvements Our previous test did not cover well the order in which the measures are added. We should make sure that the latest added record is really the correct one. Since in the future we will want to have summary for the measures have our measures nicely ordered may simplify our future tasks. We write a test to specify this point. ``` TemperatureLoggerTest >> testLatestRecord + + | office | + office := TemperatureLogger new location: 'Office'. + office recordTemperature: 19. + office recordTemperature: 18. + self assert: office latestRecord equals: 18. + office recordTemperature: 21. + self assert: office latestRecord equals: 21. ``` To be able to implement the method `latestRecord` we should understand where to the ordered collection add its elements. Let us have a look at the `add:` method of the class `OrderedCollection` implemented. ``` OrderedCollection >> add: newObject + + ^ self addLast: newObject ``` It means that the elements are added at the end of the collection, therefore the definition of the method `latestRecord` can be defined as follows: ``` TemperatureLogger >> latestRecord + + ^ measures last ``` Our tests should pass. ### Understanding design decision We want to show you that an internal representation choice such as the ordering of the elements can make a difference when implementing certain behavior. In addition we want to show you that with a good encapsulation we can change the internal representation and still get tests pass. Note that the tests also give us this agility to change and control the changes. ``` TemperatureLoggerTest >> testLatestRecords + + | office | + office := TemperatureLogger new location: 'Office'. + office recordTemperature: 16. + office recordTemperature: 17. + office recordTemperature: 19. + office recordTemperature: 18. + office recordTemperature: 21. + self assert: (office latestRecords: 3) equals: #(21 18 19) asOrderedCollection ``` ``` TemperatureLogger >> latestRecords: aNumber + + | records reversed | + records := OrderedCollection new. + reversed := measures reversed. + 1 to: aNumber do: [ :each | + records add: ( reversed at: each ) ]. + ^ records ``` It is not really nice since reversed copy the collection of measures. We can proposer another solution that goes from the end of the collection and access the correct number of elements. recordTemperature: aNumber "Add the temperature to the list of recorded measures" measures addFirst: aNumber ``` TemperatureLogger >> latestRecords: aNumber + | records | + records := OrderedCollection new. + 1 to: aNumber do: [:i | records add: (measures at: i) ]. + ^ records ``` ### Adding average computation ``` TemperatureLoggerTest >> testAverageTemperature + + | office | + office := TemperatureLogger new location: 'Office'. + office recordTemperature: 19. + office recordTemperature: 21. + office recordTemperature: 17. + + self assert: office average equals: 19 ``` There are multiple ways to implement `average`: one is to use a temporary variable representing the sum, to initialize it to zero and to loop over the measures and add to the sum each measure and finally to divide the sum by the number of measures. ``` TemperatureLogger >> average + ... ``` ### Solutions #### First test ``` TemperatureLogger >> recordCount + + ^ measures size ``` #### Average ``` TemperatureLogger >> alternateAverage + + | sum | + sum := 0. + measures do: [ :aMeasure | sum := sum + aMeasure ]. + ^ sum / measures size ``` ``` TemperatureLogger >> average + + ^ measures average ``` It is worth looking at the implementation of `average` ``` Collection >> average + ^ self sum / self size ``` What we do is that we add pair with the time and the value to our collection of measures. Since the temperature often depends on the moment during the day I want to log the date and time with each measure. Then we can request a converter for all the dates \(message `dates`\) and temperatures \(message `temperatures`\) that it contains. ``` TemperatureConverterTest >> testLocationAndDate + + | office | + office := TemperatureConverter new location: 'Office'. + "perform two measures that are logged" + office measureCelsius: 19. + office measureCelsius: 21. + + "We got effectively two measures" + self assert: office measureCount = 2. + + "All the measures were done today" + self assert: office dates equals: {Date today . Date today} asOrderedCollection. + + "We logged the correct temperature" + self assert: office temperatures equals: { 19 . 21 } asOrderedCollection ``` ``` TemperatureConverter >> measureCelsius: aTemp + measures add: DateAndTime now -> aTemp ``` To support tests we also define a method returning the number of current measure our instance holds. ``` TemperatureConverter >> measureCount + ^ measures size ``` We now define three methods returning the sequence of temperatures, the dates and the times. Since the time has a microsecond precision it is a bit difficult to test. So we only test the dates. ``` TemperatureConverter >> temperatures + ^ measures collect: [ :each | each value ] ``` ``` TemperatureConverter >> dates + ^ measures collect: [ :each | each key asDate ] ``` ``` TemperatureConverter >> times + ^ measures collect: [ :each | each key asDate ] ``` Now we can get two converters each with its own location and measurement records. The following tests verifies that this is the case. ``` TemperatureConverterTest >> testTwoLocations + + | office home | + office := TemperatureConverter new location: 'office'. + office measureFahrenheit: 19. + office measureFahrenheit: 21. + self assert: office location equals: 'office'. + self assert: office measureCount equals: 2. + home := TemperatureConverter new location: 'home'. + home measureFahrenheit: 22. + home measureFahrenheit: 22. + home measureFahrenheit: 22. + self assert: home location equals: 'home'. + self assert: home measureCount equals: 3. ``` ### The class TemperaturLogger The class `TemperaturLogger` is defined as shown below. You could have define it before defining the class `TemperaturLoggerTest` using the class definition below: ``` Object subclass: #TemperaturLogger + instanceVariableNames: '' + classVariableNames: '' + package: 'Converter' ``` This definition in essence, says that: - We want to define a new class named `TemperaturLogger`. - It has no instance or class variables \(`''` means empty string\). - It is packaged in package `Converter`. Usually when doing Test Driven Development with Pharo, we focus on tests and lets the system propose us some definitions. Then we can define the method as follows. The system may tell you that the method is an utility method since it does not use object state. It is a bit true because the converter is a _really_ simple object. For now do not care. Your test should pass. Click on the icon close to the test method to execute it. ### Converting Fahrenheit to Celcius Converting from Fahrenheit to Celsius is done with a simple linear transformation. The formula to get Fahrenheit from Celsius is F = C * 1.8 + 32. Let us write a test covering such transformation. 30 Celsius should be 86 Fahrenheit. ``` testCelsiusToFahrenheit + + | converter | + converter := TemperatureConverter new. + self assert: ((converter convertCelsius: 30) = 86.0) ``` The message `assert:` expects a boolean. Here the expression `((converter convertCelsius: 30) = 86.0)` returns a boolean. `true` if the converter returns the value 86.0, `false` otherwise. The testing frameworks also offers some other similar methods. One is particularly interesting: `assert:equals:` makes the error reporting more user friendly. The previous method is strictly equivalent to the following one using `assert:equals:`. ``` testCelsiusToFahrenheit + + | converter | + converter := TemperatureConverter new. + self assert: (converter convertCelsius: 30) equals: 86.0 ``` The message `assert:equals:` expects an expression and a result. Here `(converter convertCelsius: 30)` and `86.0`. You can use the message you prefer and we suggest to use `assert:equals:` since it will help you to understand your mistake by saying: 'You expect 86.0 and I got 30' instead of simply telling you that the result is false. Now we can define the method `convertCelcius:` as follows: ``` TemperatureConverter >> convertCelsius: anInteger + "Convert anInteger from celsius to fahrenheit" + + ^ ((anInteger * 1.8) + 32) ``` ### Define the test method \(and more\) While defining the method `testCelsiusToFahrenheit` using the class browser, the system will tell you that the class `TemperatureConverter` does not exist \(This is true because we did not define it so far\). The system will propose to create it. Just let the system do it. Once you are done. You should have two classes: `TemperatureConverterTest` and `TemperatureConverter`. As well as one method: `testCelsiusToFahrenheit`. The test does not pass since we did not implement the conversion method \(as shown by the red color in the body of `testCelsiusToFahrenheit`\). Note that you entered the method above and the system compiled it. Now in this book we want to make sure that you know about which method we are talking about hence we will prefix the method definitions with their class. For example the method `testCelsiusToFahrenheit` in the class `TemperaturLoggerTest` is defined as follows: ``` TemperaturLoggerTest >> testCelsiusToFahrenheit + + | converter | + converter := TemperatureConverter new. + self assert: (converter convertCelsius: 30) equals: 86.0 ``` ### Converting from Fahrenheit to Celsius Now you got the idea. Let us define a test for the conversion from Fahrenheit to Celsius. ``` TemperatureConverterTest >> testFahrenheitToCelsius + + | converter | + converter := TemperatureConverter new. + self assert: ((converter convertFarhenheit: 86) equals: 30.0). + self assert: ((converter convertFarhenheit: 50) equals: 10) ``` Define the method `convertFarhenheit: anInteger` ``` TemperatureConverter >> convertFarhenheit: anInteger + "Convert anInteger from fahrenheit to celsius" + + ^ ((anInteger - 32) / 1.8) ``` Run the tests they should all pass. ### About floats The conversions method we wrote returns floats. Floats are special objects in computer science because it is complex to represent infinite information \(such as all the numbers between two consecutive integers\) with a finite space \(numbers are often represented with a fixed number of bits\). In particular we should pay attention when comparing two floats. Here is a surprising case: we add two floats and the sum is not equal to their sums. ``` (0.1 + 0.2) = 0.3 +> false ``` This is because the sum is not just equal to `0.3`. The sum is in fact the number `0.30000000000000004` ``` (0.1 + 0.2) +> 0.30000000000000004 ``` To solve this problem in Pharo \(it is the same in most programming languages\), we do not use equality to compare floats but alternate messages such as `closeTo:` or `closeTo:precision:` as shown below: ``` (0.1 + 0.2) closeTo: 0.3 +> true +(0.1 + 0.2) closeTo: 0.3 precision: 0.001 +> true ``` To know more, you can have a look at the Fun with Float chapter in Deep Into Pharo \([http://books.pharo.org](http://books.pharo.org)\)\). The key point is that in computer science you should always avoid to compare the floats naively. So let us go back to our conversion: ``` ((52 - 32) / 1.8) +> 11.11111111111111 ``` In the following expression we check that the result is close to 11.1 with a precision of 0.1. It means that we accept as result 11 or 11.1 ``` ((52 - 32) / 1.8) closeTo: 11.1 precision: 0.1 +> true ``` We can use `closeTo:precision:` in our tests to make sure that we deal correctly with the float behavior we just described. ``` ((52 - 32) / 1.8) closeTo: 11.1 precision: 0.1 +> true ``` We change our tests to reflect this ``` TemperatureConverterTest >> testFahrenheitToCelsius + + | converter | + converter := TemperatureConverter new. + self assert: ((converter convertFarhenheit: 86) closeTo: 30.0 precision: 0.1). + self assert: ((converter convertFarhenheit: 50) closeTo: 10 precision: 0.1) ``` ### Printing rounded results The following expression shows that we may get converted temperature with a too verbose precision. ``` (TemperatureConverter new convertFarhenheit: 52) +>11.11111111111111 ``` Here just getting 11.1 is enough. There is no need to get the full version. In fact, we can manipulate floats in full precision but there are case like User Interfaces where we would like to get a shorter sort of information. Typically as user of the temperature converter, our body does not see the difference between 12.1 or 12.2 degrees. Pharo libraries include the message `printShowingDecimalPlaces: aNumberOfDigit` used to rounds the _textual_ representation of a float. ``` (TemperatureConverter new convertFarhenheit: 52) printShowingDecimalPlaces: 1 +>11.1 ``` ### Building a map of degrees Often when you are travelling you would like to have kind of a map of different degrees as follows: Here we want to get the converted values between 50 to 70 farhenheit degrees. ``` (TemperatureConverter new convertFarhenheitFrom: 50 to: 70 by: 2). +> { 50->10.0. + 52->11.1. + 54->12.2. + 56->13.3. + 58->14.4. + 60->15.6. + 62->16.7. + 64->17.8. + 66->18.9. + 68->20.0. + 70->21.1} ``` What we see is that the method `convertFarhenheitFrom:to:by:` returns an array of pairs. A pair is created using the message `->` and we can access the pair elements using the message `key` and `value` as shown below. ``` | p1 | +p1 := 50 -> 10.0. +p1 key +> 50 +p1 value +> 10.0 ``` Let us write a test first. We want to generate map containing as key the farhenheit and as value the converted celsius. Therefore we will get a collection with the map named `results` and a collection of the expected values that the value of the elements should have. On the two last lines of the test method, using the message `with:do:` we iterate on both collections in parallel taking on element of each collection and compare them. ``` TemperatureConverterTest >> testFToCScale + + | converter results expectedCelsius | + converter := TemperatureConverter new. + results := (converter convertFarhenheitFrom: 50 to: 70 by: 2). + expectedCelsius := #(10.0 11.1 12.2 13.3 14.4 15.5 16.6 17.7 18.8 20.0 21.1). + + results with: expectedCelsius + do: [ :res :cel | res value closeTo: cel ] ``` Now we are ready to implement the method `convertFarhenheitFrom: low to: high by: step`. Using the message `to:by:`, we create an interval to generate the collection of numbers starting at low and ending up at high using the increment step. Then we use the message `collect:` which applies a block to a collection and returns a collection containing all the values returned by the block application. Here we just create a pair whose key is the farhenheit and whose value is its converted celsius value. ``` TemperatureConverter >> convertFarhenheitFrom: low to: high by: step + "Returns a collection of pairs (f, c) for all the farhenheit temperatures from a low to an high temperature" + + ^ (low to: high by: step) + collect: [ :f | f -> (self convertFarhenheit: f) ] ``` ### Spelling Fahrenheit correctly! You may not noticed but we badly spelled fahrenheit since the beginning of this chapter. Fahrenheit is not spelt Farhenheit but Fahrenheit. Now you may start to think that I'm crazy, because you should rename all the methods you wrote and in addition all the users of such methods and after we should check that we did not break anything. And you can think that this is a huge task. Well first you should rename the methods because nobody wants to keep badly named code. Second, I'm not crazy at all. Programmers rename their code regularly because they often do not get it right the first time, or even the second time... Often you rewrite your code after thinking more about the interface you finally understand that you should propose. In fact good designer think a lot about names because names convey the intent of a computation. Now we have two super powerful tools to help us: Refactorings and Tests. We will use the **Rename method** refactoring proposed by Pharo. A refactoring garantees that not only the method but all the places where it is called will also be renamed to send the new message. In addition a refactoring garantees that the behavior of the program is not modified. So this is really powerful. Select the method `convertFarhenheit:` in the method list and bring the menu, use the **Rename method \(all\)** item, give a new name `convertFahrenheit:`. The system will prompt you to show you all the corresponding operations. Check them to see what you should have done manually. Imagine the amount of mistakes you could have made and proceed. Do the same for `convertFahrenheitFrom:to:by:`. Now the key question is if these changes broke anything. Normally everything should work since this is what we expect when using refactorings. But runnning the tests has the final word. So run the tests to check if everything is ok and here is a clear use of tests: they ensure that we can spot fast a regression. With this little scenario you should have learned two important things: - Tests are written once and executed million times to check for regression. - Refactorings are really powerful operations that save us from tedious manual rewriting. ### We can add now a new method to convert fahrenheit to celcius and we define a new test first. ``` TemperatureConverterTest >> testLocationAndDateWithConversion + + | converter | + converter := TemperatureConverter new location: 'Lille'. + converter measureFahrenheit: 86. + converter measureFahrenheit: 50. + self assert: converter measureCount equals: 2. + self assert: converter dates + equals: {Date today . Date today} asOrderedCollection. + self assert: converter temperatures + equals: { converter convertFahrenheit: 86 . + converter convertFahrenheit: 50 } asOrderedCollection ``` What we do is that since celsius is the scientific unity for temperature we convert to celsius before recording our temperature. ``` TemperatureConverter >> measureFahrenheit: aTemp + measures add: DateAndTime now -> (self convertFahrenheit: aTemp) ``` ### Discussion From a design perspective we see that the logger behavior is a much better object than the converter. The logger keeps some internal data specific to a location while the converter is stateless. Object-oriented programming is much better for capturing object with state. This is why the converter was a kind of silly objects but it was to get you started. Now it is rare that the world we want to model and represent is stateless. This is why object-oriented programming is a powerful way to develop complex programs. ### Conclusion In this chapter we built a simple temperature converter. We showed how define and execute unit tests using a Test Driven approach. The interest in testing and Test Driven Development is not limited to Pharo. Automated testing has become a hallmark of the _Agile software development_ movement, and any software developer concerned with improving software quality would do well to adopt it. We showed that tests are an important aid to measure our progress and also are an important aid to define clearly what we want to develop. \ No newline at end of file diff --git a/Chapters/Converter/ConverterSolution.md b/Chapters/Converter/ConverterSolution.md new file mode 100644 index 0000000..3b89622 --- /dev/null +++ b/Chapters/Converter/ConverterSolution.md @@ -0,0 +1,7 @@ +## Converter solution This chapter contains the solution of Chapter *@cha_converter@*. ### Converting from Farhenheit to Celsius ``` TemperatureConverter >> convertFarhenheit: anInteger + "Convert anInteger from fahrenheit to celsius" + + ^ ((anInteger - 32) / 1.8) ``` ``` TemperatureConverter >> measureCount + ^ measures size ``` ### Adding logging behavior ``` TemperatureConverter >> dates + ^ measures collect: [ :each | each key asDate ] ``` ``` TemperatureConverter >> measureFahrenheit: aTemp + measures add: DateAndTime now -> (self convertFahrenheit: aTemp) ``` \ No newline at end of file diff --git a/Chapters/Counter/Counter.md b/Chapters/Counter/Counter.md new file mode 100644 index 0000000..03af52b --- /dev/null +++ b/Chapters/Counter/Counter.md @@ -0,0 +1,49 @@ +## Developing a simple counter To get started in Pharo, we invite you to implement a simple counter by following the steps given below. In this exercise you will learn how to create packages classes, methods, instances. You will learn how to define tests and more. This simple tutorial covers most of the important actions that we do when developing in Pharo. Note that the development flow promoted by this little tutorial is _traditional_ in the sense that you will define a package, a class, _then_ define its instance variable _then_ define its methods _and_ finally execute it \(See the companion video below\). Now in Pharo, developers often follow a _totally_ different style \(that we call live coding\) where they execute an expression that raises errors and they code in the debugger and let the system define some instance variables and methods on the fly for them. Once you will have finished this tutorial, you will feel more confident with Pharo and we strongly suggest you to try the other style \(See the second video showing such different development practices\). #### Companion videos You can find at [http://mooc.pharo.org/](http://mooc.pharo.org/), the mooc videos with french and english voice over and subtitles in different languages \(japanese, spanish, french, english\). Among such videos, two videos are related to the counter exercises. They are called "Redo" because this is left to the reader to follow and redo the videos. - Coding the traditional way: [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W1/](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W1/) and [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos-EN/W2/](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos-EN/W2/) - Coding in the debugger: [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/) and [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos-EN/W2/](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos-EN/W2/) ### Our use case Here is our use case: we want to be able to create a counter, increment it twice, decrement it and check that its value is correct. It looks like this little use case will fit perfectly a unit test - you will define one later. ``` | counter | +counter := Counter new. +counter increment; increment. +counter decrement. +counter count = 1 ``` Now we will develop all the mandatory class and methods to support this scenario. ![Package created and class creation template.](figures/CounterPackageCreated.png label=figpackageCreated) ### Create your own class In this part, you will create your first class. In Pharo, a class is defined in a package. You will create a package then a class. The steps we will do are the same ones every time you create a class, so memorize them well. #### Create a package Using the Browser create a package. The system will ask you a name, write `MyCounter`. This new package is then created and added to the list. Figure *@figpackageCreated@* shows the result of creating such a package. #### Create a class Creating a class requires four steps. They consist basically in editing the class definition template to specify the class you want to create. - By default, the system helps you to define a subclass of the class `Object`. This is why it is written `Object subclass: #NameOfSubclass`. - **Class Name.** You should fill in the name of your class by replacing the word `NameOfSubclass` with the word `Counter`. Take care that the name of the class starts with a capital letter and that you do not remove the #sign in front of `NameOfClass`. This is because the class we want to create does not exist yet, so we have to give its name, and we use a Symbol \(a unique string in Pharo\) to do so. - **Instance variable definition.** Then, you should fill in the names of the instance variables of this class. We need one instance variable called `count`. Take care that you leave the string quotes! - **Class variable definition**. As we do not need any class variable make sure that the argument for the class instance variables is an empty string `classInstanceVariableNames: ''`. You should get the following class definition. ``` Object subclass: #Counter + instanceVariableNames: 'count' + classVariableNames: '' + package: 'MyCounter' ``` Now we should compile it. We now have a filled-in class definition for the class `Counter`. To define it, we still have to _compile_ it. Therefore, select the accept menu item. The class `Counter` is now compiled and immediately added to the system. Figure *@figclassCreated@* illustrates the resulting situation that the browser should show. ![Class created.](figures/CounterClassCreated.png label=figclassCreated) The tool runs automatically some code critic and some of them are just inaccurate, so do not care for now. As we are disciplined developers, we add a comment to `Counter` class by clicking Comment button. You can write the following comment: ``` Counter is a simple concrete class which supports incrementing and decrementing a counter. +Its API is +- decrement, increment +- count +Its creation API is message withValue: ``` Select menu item 'accept' to store this class comment in the class. ### Define protocols and methods In this part you will use the browser to learn how to add protocols and methods. The class we have defined has one instance variable named `count`. You should remember that in Pharo, \(1\) everything is an object, \(2\) instance variables are private to the object, and \(3\) the only way to interact with an object is by sending messages to it. Therefore, there is no other mechanism to access the instance variable values from outside an object than sending a message to the object. What you can do is to define messages that return the value of the instance variable. Such methods are called _accessors_, and it is a common practice to always define and use them. We start to create an accessor method for our instance variable `count`. A method is usually sorted into a protocol. These protocols are just a group of methods without any language semantics, but convey important navigation information for the reader of your class. You get protocol named: `'testing'` for method performing tests, `'printing'` for methods displaying the object, `'accessing'` for simple accessor methods and so on. Although protocols can have any name, Pharo programmers follow certain conventions for naming these protocols. But don't be stressed if you do not name well your protocols. #### Create a method Now let us create the accessor methods for the instance variable `count`. Start by selecting the class `Counter` in a browser, and make sure that you are editing the instance side of the class \(i.e., we define methods that will be sent to instances\) by deselecting the Class side radio button. Create a new protocol by bringing the menu of methods protocol list: click on the third list from the left. Select the newly created protocol. Then in the bottom pane, the edit field displays a method template laying out the default structure of a method. As a general hint, double click at the end of or beginning of the text and start typing your method. Replace the template with the following method definition: ``` count + "Return the current value of the receiver" + ^ count ``` This defines a method called `count`, taking no arguments, having a method comment and returning the instance variable `count`. Then choose _accept_ in the menu to compile the method. You can now test your new method by typing and evaluating the next expression in a Playground, or any text editor. ``` Counter new count +>>> nil ``` This expression first creates a new instance of `Counter`, and then sends the message `count` to it. It retrieves the current value of the counter. This should return `nil` \(the default value for non-initialised instance variables\). Afterwards we will create instances with a reasonable default initialisation value. #### Adding a setter method Another method that is normally used besides the accessor method is a so-called setter method. Such a method is used to change the value of an instance variable from a client. For example, the expression `Counter new count: 7` first creates a new `Counter` instance and then sets its value to 7: The snippets shows that the counter effectively contains its value. ``` | c | +c := Counter new count: 7. +c count +>>> 7 ``` This setter method does not currently exist, so as an exercise write the method `count:` such that, when invoked on an instance of `Counter`, instance variable is set to the argument given to the message. Test your method by typing and evaluating the expression above. ### Define a Test Class Writing tests is an important activity that will support the evolution of your application. Remember that a test is written _once and executed million_ times. For example if we have turned the expression above into a test we could have checked automatically that our new method is correctly working. To define a test case we will define a class that inherits from `TestCase`. Therefore define a class named `CounterTest` as follows: ``` TestCase subclass: #CounterTest + instanceVariableNames: '' + classVariableNames: '' + package: 'MyCounter' ``` Now we can write a first test by defining one method. Test methods should start with _test_ to be automatically executed by the TestRunner or when you press on the icon of the method. Now to make sure that you understand in which class we define the method we prefix the method body with the class name and `>>`. `CounterTest>>` means that the method is defined in the class `CounterTest`. Define the following method. It first creates an instance, sets its value and verifies that the value is correct. The message `assert:equals:` is a special message verifying if the test passed or not. ``` CounterTest >> testCountIsSetAndRead + | c | + c := Counter new. + c count: 7. + self assert: c count equals: 7 ``` Verify that the test passes by executing either pressing the icon in front of the method or using the TestRunner available in the Tools menu \(selecting your package\). Since you have a first green test. This is a good moment to save your work. ![Selecting a new kind of repository to the list of possible places to commit the package.](figures/CounterAddingRepo.png label=figSelectingARepo) ![Editing the repository information.](figures/CounterEditingProject.png label=figAddingARepo) ### Saving your work Several ways to save your work exist. - _Using plain files_. You can save the class or a method by clicking on it and selecting the fileout menu item. You will get a file containing the source code on your hard-disk - This is not the favorite way to save your code. - _Using a version control system_. It is better to use a version control system. In Pharo you can use Monticello and Git \(even if it is more for advanced users\). In this chapter, we explain the simplest way to get you done. Note that the complete set of Pharo packages is managed via Monticello \(which is a distributed versioning control system - there are chapters in **Pharo by Example** and **Deep into Pharo** books [http://books.pharo.org](http://books.pharo.org)\). Use the **Monticello Browser** \(available in Tools\) to save your work. You can save a package locally on your hard-disk or on a remote server on the web such as [http://www.smalltalkhub.com](http://www.smalltalkhub.com) #### Saving using Monticello Using Monticello you can save your work: - _Locally_. You can store your packages in a folder on your disk \(use directory as a kind of repository below\). - _Remotely_. Using an account on a free server such [http://www.smalltalkhub.com/](http://www.smalltalkhub.com/). You can save your work and share it with others. Note each time you load or save a package, this package is also be stored in the folder named 'package-cache' on your hard-disk. ##### Add a repository Go to [http://www.smalltalkhub.com/](http://www.smalltalkhub.com/) and create a member account then register a new project. You get an HTTP entry that refers to your project. Define a new HTTP repository using the Monticello Browser as shown by Figures *@figSelectingARepo@* and *@figAddingARepo@*. Figure *@figSelectingARepo@* shows that your package is dirty: this is indicated with the little '*' in front of the packages. **Example.** As authors we are saving the examples for this chapter as a special team named PharoMooc in the Counter project so our information is the following: ``` MCHttpRepository + location: 'http://smalltalkhub.com/mc/PharoMooc/Counter/main' + user: '' + password: '' ``` Now for you, you should adapt the following template to use your own information: ``` MCHttpRepository + location: 'http://smalltalkhub.com/mc/YourAccount/YourProject/main' + user: 'YourAccountID' + password: 'YourAccountPassword' ``` ##### Saving your package To save your work, simply select your package and the repository you want to save it to and save it using the Save button. This will open a dialog where you can give a comment, version numbers and blessing. From then on, other people can load it from there, in the same way that you would use cvs or other multi-user versioning systems. Saving the image is also a way to save your working environment, but not a way to version and publish it in a way that can be easily shared. You can of course both publish your package \(so that other people can load it, and that you can compare it with other versions, etc.\) and save your image \(so that next time that you start your image you are in the same working environment\). ### Adding more messages Before implementing the following messages we define first a test. We define one test for the method `increment` as follows: ``` CounterTest >> testIncrement + | c | + c := Counter new. + c count: 0 ; increment; increment. + self assert: c count equals: 2 ``` Here we create a counter, set its value to 0, send it the message increment two times and verify that we get a counter of value 2. Now you should implement some more methods. - Propose a definition for the method `increment` and implement it. - Implement also a new test method for the method `decrement`. - Define the method `decrement` place it together with `increment` in the protocol `'operation'`. Here are the possible definitions for such methods. ``` Counter >> increment + count := count + 1 ``` ``` Counter >> decrement + count := count - 1 ``` ![Class with green tests.](figures/CounterClassTestCreated.png label=figclassTestCreated) Run your tests they should pass \(as shown in Figure *@figclassTestCreated@*\). Again this is a good moment to save your work. Saving at point where tests are green is always a good process. ### Instance initialization method Right now the initial value of our counter is not set as the following expression shows it. ``` Counter new count +>>> nil ``` Let us write a test checking that a newly created instance has 0 as a default value. ``` CounterTest >> testValueAtCreationTimeIsZero + self assert: Counter new count equals: 0 ``` If you run it, it will turn yellow indicating a failure \(a situation that you anticipated but that is not correct\) - by opposition to an error which is an anticipated situation leading to failed assertion. #### Define an initialize method Now we have to write an initialization method that sets a default value of the `count` instance variable. However, as we mentioned the `initialize` message is sent to the newly created instance. This means that the `initialize` method should be defined at the instance side as any method that is sent to an instance of `Counter` \(like `increment`\) and `decrement`. The `initialize` method is responsible to set up the default value of instance variables. Therefore at the instance side, you should create a protocol `initialization`, and create the following method \(the body of this method is left blank. Fill it in!\). ``` Counter >> initialize + "set the initial value of the value to 0" + + count := 0 ``` Now create a new instance of class `Counter`. Is it initialized by default? The following code should now work without problem: ``` Counter new increment ``` and the following one should return 2 ``` Counter new increment; increment; count +>>> 2 ``` But better write a test since we will execute it all the time. ``` TestCounter >> testCounterWellInitialized + self + assert: Counter new increment; increment; count + equals: 2 ``` Again save your work ### Better object description When you select the expression `Counter new` and print its result \(using the Print it menu of the editor\) you obtain a simple string `'a Counter'`. ``` Counter new +>>> a Counter ``` We would like to get a much richer information for example knowing the counter value. Implement the following methods in the protocol `printing` ``` Counter >> printOn: aStream + super printOn: aStream. + aStream nextPutAll: ' with value: ', self count printString. ``` Note that the method `printOn:` is used when you print an object using print it \(See Figure *@figBetterDescription@*\). In addition this method is invoked when you click on `self` in an inspector. An inspector is an object to interact and modify objects. It is really powerful during development. ![Better description doing a Print It \(cmd + P\).](figures/CounterBetterDescription.png label=figBetterDescription) ### Conclusion In this chapter you learned how to define packages, classes, methods, and define tests. The flow of programming that we chose for this first tutorial is similar to most of programming languages. In Pharo you can use a different flow that is based on defining a test first, executing it and when the execution raises error to define the corresponding classes, methods, and instance variables often from inside the debugger. We suggest you now to redo the exercise following the second companion video. \ No newline at end of file diff --git a/Chapters/DSL/DSL.md b/Chapters/DSL/DSL.md new file mode 100644 index 0000000..69c4ee4 --- /dev/null +++ b/Chapters/DSL/DSL.md @@ -0,0 +1,81 @@ +## Crafting a simple embedded DSL with Pharo @cha:dsl In this chapter you will develop a simple domain specific language \(DSL\) for rolling dice. Players of games such as Dungeons & Dragons are familiar with such DSL. An example of such DSL is the following expression: `2 D20 + 1 D6` which means that we should roll two 20-faces dices and one 6 faces die. It is called an embedded DSL because the DSL uses the syntax of the language used to implement it. Here we use the Pharo syntax to implement the Dungeons & Dragons rolling die language. This little exercise shows how we can \(1\) simply reuse traditional operator such as `+`, \(2\) develop an embedded domain specific language and \(3\) use class extensions \(the fact that we can define a method in another package than the one of the class of the method\). ### Getting started Using the code browser, define a package named `Dice` or any name you like. #### Create a test It is always empowering to verify that the code we write is always working as we defining it. For this purpose you should create a unit test. Remember unit testing was promoted by K. Beck first in the ancestor of Pharo. Nowadays this is a common practice but this is always useful to remember our roots! Define the class `DieTest` as a subclass of `TestCase` as follows: ``` TestCase subclass: #DieTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Dice' ``` What we can test is that the default number of faces of a die is 6. ``` DieTest >> testInitializeIsOk + self assert: Die new faces equals: 6 ``` If you execute the test, the system will prompt you to create a class `Die`. Do it. #### Define the class Die The class `Die` inherits from `Object` and it has an instance variable, `faces` to represent the number of faces one instance will have. Figure *@figOneClassDiceDesign@* gives an overview of the messages. ![A single class with a couple of messages. Note that the method `withFaces:` is a class method.](figures/OneClassDiceDesign.pdf width=25&label=figOneClassDiceDesign) ``` Object subclass: + ... Your solution ... ``` In the `initialization` protocol, define the method `initialize` so that it simply sets the default number of faces to 6. ``` Die >> initialize + ... Your solution ... ``` Do not hesitate to add a class comment. Now define a method to return the number of faces an instance of `Die` has. ``` Die >> faces + ^ faces ``` Now your tests should all pass \(and turn green\). ### Rolling a die To roll a die you should use the method from Number `atRandom` which draws randomly a number between one and the receiver. For example `10 atRandom` draws number between 1 to 10. Therefore we define the method `roll`: ``` Die >> roll + ... Your solution ... ``` Now we can create an instance `Die new` and send it the message `roll` and get a result. Do `Die new inspect` to get an inspector and then type in the bottom pane `self roll`. You should get an inspector like the one shown in Figure *@figDiceNoDetail@*. With it you can interact with a die by writing expression in the bottom pane. ![Inspecting and interacting with a die.](figures/DiceNoDetail.png width=60&label=figDiceNoDetail) ### Creating another test But better, let us define a test that verifies that rolling a new created dice with a default 6 faces only returns value comprised between 1 and 6. This is what the following test method is actually specifying. ``` DieTest >> testRolling + | d | + d := Die new. + 10 timesRepeat: [ self assert: (d roll between: 1 and: 6) ] ``` !!important Often it is better to define the test even before the code it tests. Why? Because you can think about the API of your objects and a scenario that illustrate their correct behavior. It helps you to program your solution. ### Instance creation interface We would like to get a simpler way to create `Die` instances. For example we want to create a 20-faces die as follows: `Die withFaces: 20` instead of always have to send the new message to the class as in `Die new faces: 20`. Both expressions are creating the same die but one is shorter. Let us look at it: - In the expression `Die withFaces:`, the message `withFaces:` is sent to the class `Die`. It is not new, we constantly sent the message `new` to `Die` to created instances. - Therefore we should define a method that will be executed Let us define a test for it. ``` DieTest >> testCreationIsOk + self assert: (Die withFaces: 20) faces equals: 20 ``` What the test clearly shows is that we are sending a message to the **class** `Die` itself. #### Defining a class method Define the _class_ method `withFaces:` as follows: - Click on the class button in the browser to make sure that you are editing a **class** method. - Define the method as follows: ``` Die class >> withFaces: aNumber + "Create and initialize a new die with aNumber faces." + | instance | + instance := self new. + instance faces: aNumber. + ^ instance ``` Let us explain this method - The method ` withFaces:` creates an instance using the message `new`. Since `self` represents the receiver of the message and the receiver of the message is the class `Die` itself then `self` represents the class `Die`. - Then the method sends the message `faces:` to the instance and - Finally returns the newly created instance. Pay really attention that a class method `withFaces:` is sent to a class, and an instance method sent to the newly created instance `faces:`. Note that the class method could have also named `faces:` or any name we want, it does not matter, it is executed when the receiver is the class `Die`. This test will not work since we did not create yet the method `faces:`. This is now the time to define it. Pay attention the method `faces:` is sent to an instance of the class `Die` and not the class itself. It is an instance method, therefore make sure that you deselected the class button before editing it. ``` Die >> faces: aNumber + faces := aNumber ``` Now your tests should run. So even if the class `Die` could implement more behavior, we are ready to implement a die handle. !!important A class method is a method executed in reaction to messages sent to a _class_. It is defined on the class side of the class. In `Die withFaces: 20`, the message `withFaces:` is sent to the class `Die`. In `Die new faces: 20`, the message `new` is sent to the _class_ `Die` and the message `faces:` is sent to the _instance_ returned by `Die new`. #### \[Optional\] Alternate instance creation definition In a first reading you can skip this section. The _class_ method definition `withFaces:` above is strictly equivalent to the one below. ``` Die class >> withFaces: aNumber + ^ self new faces: aNumber; yourself ``` Let us explain it a bit. `self` represents the class `Die` itself. Sending it the message `new`, we create an instance and send it the `faces:` message. And we return the expression. So why do we need the message `yourself`. The message `yourself` is needed to make sure that whatever value the instance message `faces:` returns, the instance creation method we are defining returns the new created instance. You can try to redefine the instance method `faces:` as follows: ``` Die >> faces: aNumber + faces := aNumber. + ^ 33 ``` Without the use of `yourself`, `Die withFaces: 20` will return 33. With `yourself` it will return the instance. The trick is that `yourself` is a simple method defined on `Object` class: The message `yourself` returns the receiver of a message. The use of `;` sends the message to the receiver of the previous message \(here `faces:`\). The message `yourself` is then sent to the object resulting from the execution of the expression `self new` \(which returns a new instance of the class `Die`\), as a consequence it returns the new instance. ### First specification of a die handle Let us define a new class `DieHandle` that represents a die handle. The following code snippet shows the API that we would like to offer for now \(as shown in Figure *@fig:DiceDesign@*\). We create a new handle then add some dice to it. We will use this kind of expressions in future tests below. ![A die handle is composed of dice.](figures/DiceDesign.pdf width=60&label=fig:DiceDesign) ``` DieHandle new + addDie: (Die withFaces: 6); + addDie: (Die withFaces: 10); + yourself ``` Of course we will define tests first for this new class. We define the class `DieHandleTest`. ``` TestCase subclass: #DieHandleTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Dice' ``` #### Testing a die handle We define a new test method as follows. We create a new handle and add one die of 6 faces and one die of 10 faces. We verify that the handle is composed of two dice. ``` DieHandleTest >> testCreationAdding + | handle | + handle := DieHandle new + addDie: (Die withFaces: 6); + addDie: (Die withFaces: 10); + yourself. + self assert: handle diceNumber = 2. ``` In fact we can do it better. Let us add a new test method to verify that we can even add two dice having the same number of faces. ``` DieHandleTest >> testAddingTwiceTheSameDice + | handle | + handle := DieHandle new. + handle addDie: (Die withFaces: 6). + self assert: handle diceNumber = 1. + handle addDie: (Die withFaces: 6). + self assert: handle diceNumber = 2. ``` Now that we specified what we want, we should implement the expected class and messages. Easy! ### Defining the DieHandle class The class `DieHandle` inherits from `Object` and it defines one instance variable to hold the dice it contains. ``` Object subclass: ... + ... Your solution ... ``` We simply initialize it so that its instance variable `dice` contains an instance of `OrderedCollection`. ``` DieHandle >> initialize + ... Your solution ... ``` Then define a simple method `addDie:` to add a die to the list of dice of the handle. You can use the message `add:` sent to a collection. ``` DieHandle >> addDie: aDie + ... Your solution ... ``` Now you can execute the code snippet and inspect it. You should get an inspector as shown in Figure *@DieHandleNoDetail@* ``` DieHandle new + addDie: (Die withFaces: 6); + addDie: (Die withFaces: 10); + yourself ``` ![Inspecting a DieHandle.](figures/DiceHandleNoDetail.png width=60&label=DieHandleNoDetail) Finally we should add the method `diceNumber` to the `DieHandle` class to be able to get the number of dice of the handle. We just return the size of the dice collection. ``` DieHandle >> diceNumber + ^ dice size ``` Now your tests should run and this is a good moment to save and publish your code. ### Improving programmer experience Now when you open an inspector you cannot see well the dice that compose the die handle. Click on the `dice` instance variable and you will only get a list of `a Dice` without further information. What we would like to get is something like `a Die (6)` or `a Die (10)` so that in a glance we know the faces a die has. ``` DieHandle new + addDie: (Die withFaces: 6); + addDie: (Die withFaces: 10); + yourself ``` This is the message `printOn:` that is responsible to provide a textual representation of the message receiver. By default, it just prints the name of the class prefixed with `'a'` or `'an'`. So we will enhance the `printOn:` method of the `Die` class to provide more information. Here we simply add the number of faces surrounded by parenthesis. The `printOn:` message is sent with a stream as argument. This is in such stream that we should add information. We use the message `nextPutAll:` to add a number of characters to the stream. We concatenate the characters to compose `()` using the message `,` comma defined on collections \(and that concatenate collections and strings\). ``` Die >> printOn: aStream + + super printOn: aStream. + aStream nextPutAll: ' (', faces printString, ')' ``` Now in your inspector you can see effectively the number of faces a die handle has as shown by Figure *@diceDetail@* and it is now easier to check the dice contained inside a handle \(See Figure *@DieHandleDetail@*\). ![Die details.](figures/DiceDetail.png width=70&label=diceDetail) ![A die handle with more information.](figures/DiceHandleDetail.png width=90&label=DieHandleDetail) **Note** This implementation of `printOn:` is suboptimal. Indeed during the message `faces printString`, it creates a separate stream instead of using the one pass as argument. To understand the problem you can have a look at the implementation of the method `printString` defined in the class `Object`. ``` Die >> printOn: aStream + + super printOn: aStream. + aStream + nextPutAll: '('; + print: faces; + nextPutAll: ')' ``` ### Rolling a die handle Now we can define the rolling of a die handle by simply summing result of rolling each of its dice. Implement the `roll` method of the `DieHandle` class. This method must collect the results of rolling each dice of the handle and sum them. You may want to have a look at the method `sum:` in the class `Collection` or use a simple loop such as `do:` to iterate over the dice. ``` DieHandle >> roll + ... Your solution ... ``` Now we can send the message `roll` to a die handle. ``` handle := DieHandle new + addDie: (Die withFaces: 6); + addDie: (Die withFaces: 10); + yourself. +handle roll ``` Define a test to cover such behavior. Rolling an handle of n dice should be between n and the sum of the face number of each die. ``` DieHandleTest >> testRoll + ... Your solution ... ``` ### About Dice and DieHandle API It is worth to spend some times looking at the relationship between `DieHandle` and `Dice`. A die handle is composed of dices. What is an important design decision is that the API of the main behavior \(`roll`\) is the same for a die or a die handle. You can send the message `roll` to a dice or a die handle. This is an important property. Why? Because it means that from a client perspective, she/he can treat the receiver without having to take care about the kind of object it is manipulating. A client just sends the message `roll` to an object and get back a number \(as shown in Figure *@figDieHandleComposition@*\). The client is not concerned by the fact that the receiver is composed out a simple object or a complex one. Such design decision supports the _Don't ask, tell_ principle. ![A polymorphic API supports the _Don't ask, tell_ principle.](figures/DiceHandleComposition.pdf width=60&label=figDieHandleComposition) !!important Offering polymorphic API is a tenet of good object-oriented design. It enforces the _Don't ask, tell_ principle. Clients do not have to worry about the type of the objects to whom they talk to. For example we can write the following expression that adds a die and a dieHandle to a collection and collect the different values \(we convert the result into an array so that we can print it in the book\). ``` | col | +col := OrderedCollection new. +col add: (Die withFaces: 20). +col add: (DieHandle new addDie: (Die withFaces: 4); yourself). +(col collect: [:each | each roll]) asArray +>>> #(17 3) ``` #### About composition Composite objects such document objects \(a book is composed of chapters, a chapter is composed of sections, a section is composed of paragraphs\) have often a more complex composition relationship than the composition between die and die handle. Often the composition is recursive in the sense that an element can be the whole: for example, a diagram can be composed of lines, circles, and other diagrams. We will see an example of such composition in the Expression Chapter *@cha:expressions@*. ### Role playing syntax Now we are ready to offer a syntax following practice of role playing game, i.e., using `2 D20` to create a handle of two dice with 20 faces each. For this purpose we will define class extensions: we will define methods in the class `Integer` but these methods will be only available when the package Dice will be loaded. But first let us specify what we would like to obtain by writing a new test in the class `DieHandleTest`. Remember to always take any opportunity to write tests. When we execute `2 D20` we should get a new handle composed of two dice and can verify that. This is what the method `testSimpleHandle` is doing. ``` DieHandleTest >> testSimpleHandle + self assert: 2 D20 diceNumber = 2. ``` Verify that the test is not working! It is much more satisfactory to get a test running when it was not working before. Now define the method `D20` with a protocol named `*NameOfYourPackage` \(`'*Dice`' if you named your package `'Dice'`\). The `*` \(star\) prefixing a protocol name indicates that the protocol and its methods belong to another package than the package of the class. Here we want to say that while the method `D20` is defined in the class `Integer`, it should be saved with the package `Dice`. The method `D20` simply creates a new die handle, adds the correct number of dice to this handle, and returns the handle. ``` Integer >> D20 + ... Your solution ... ``` #### About class extensions We asked you to place the method `D20` in a protocol starting with a star and having the name of the package \(`'*Dice'`\) because we want this method to be saved \(and packaged\) together with the code of the classes we already created \(`Die`, `DieHandle`,...\) Indeed in Pharo we can define methods in classes that are not defined in our package. Pharoers call this action a class extension: we can add methods to a class that is not ours. For example `D20` is defined on the class `Integer`. Now such methods only make sense when the package `Dice` is loaded. This is why we want to save and load such methods with the package we created. This is why we are defining the protocol as `'*Dice'`. This notation is a way for the system to know that it should save the methods with the package and not with the package of the class `Integer`. Now your tests should pass and this is probably a good moment to save your work either by publishing your package and to save your image. We can do the same for the default dice with different faces number: 4, 6, 10, and 20. But we should avoid duplicating logic and code. So first we will introduce a new method `D:` and based on it we will define all the others. Make sure that all the new methods are placed in the protocol `'*Dice'`. To verify you can press the button Browse of the Monticello package browser and you should see the methods defined in the class `Integer`. ``` Integer >> D: anInteger + ... Your solution ... ``` ``` Integer >> D4 + ^ self D: 4 ``` ``` Integer >> D6 + ^ self D: 6 ``` ``` Integer >> D10 + ^ self D: 10 ``` ``` Integer >> D20 + ^ self D: 20 ``` We have now a compact form to create dice and we are ready for the last part: the addition of handles. ### Handle's addition Now what is missing is that possibility to add several handles as follows: `2 D20 + 3 D10`. Of course let's write a test first to be clear on what we mean. ``` DieHandleTest >> testSumming + | handle | + handle := 2 D20 + 3 D10. + self assert: handle diceNumber = 5. ``` We will define a method `+` on the `DieHandle` class. In other languages this is often not possible or is based on operator overloading. In Pharo `+` is just a message as any other, therefore we can define it on the classes we want. Now we should ask ourself what is the semantics of adding two handles. Should we modify the receiver of the expression or create a new one. We preferred a more functional style and choose to create a third one. The method `+` creates a new handle then add to it the dice of the receiver and the one of the handle passed as argument to the message. Finally we return it. ``` DieHandle >> + aDieHandle + ... Your solution ... ``` Now we can execute the method `(2 D20 + 1 D6) roll` nicely and start playing role playing games, of course. ### Conclusion This chapter illustrates how to create a small DSL based on the definition of some domain classes \(here `Dice` and `DieHandle`\) and the extension of core class such as `Integer`. It also shows that we can create packages with all the methods that are needed even when such methods are defined on classes external \(here `Integer`\) to the package. It shows that in Pharo we can use usual operators such as `+` to express natural models. \ No newline at end of file diff --git a/Chapters/DSL/DSLSolution.md b/Chapters/DSL/DSLSolution.md new file mode 100644 index 0000000..47394b1 --- /dev/null +++ b/Chapters/DSL/DSLSolution.md @@ -0,0 +1,40 @@ +## Die DSL @cha:dslsolution Here are the possible solutions of the implementation we asked for the DSL Chapter *@cha:dsl@*. #### Define class Die ``` Object subclass: #Die + instanceVariableNames: 'faces' + classVariableNames: '' + package: 'Dice' ``` ``` Die >> initialize + super initialize. + faces := 6 ``` #### Rolling a die ``` Die >> roll + ^ faces atRandom ``` #### Define class DieHandle ``` Object subclass: #DieHandle + instanceVariableNames: 'dice' + classVariableNames: '' + package: 'Dice' ``` ``` DieHandle >> initialize + super initialize. + dice := OrderedCollection new. ``` #### Die addition ``` DieHandle >> addDie: aDie + dice add: aDie ``` ### Rolling a dice handle ``` DieHandleTest >> testRoll + | handle | + handle := DieHandle new + addDie: (Die withFaces: 6); + addDie: (Die withFaces: 10); + yourself. + 1000 timesRepeat: [ handle roll between: 2 and: 16 ] ``` ``` DieHandle >> roll + + | res | + res := 0. + dice do: [ :each | res := res + each roll ]. + ^ res ``` ### Role playing syntax ``` Integer >> D20 + | handle | + handle := DieHandle new. + self timesRepeat: [ handle addDie: (Die withFaces: 20)]. + ^ handle ``` ``` Integer >> D: anInteger + + | handle | + handle := DieHandle new. + self timesRepeat: [ handle addDie: (Die withFaces: anInteger)]. + ^ handle ``` ### Adding DieHandles ``` DieHandle >> + aDieHandle + "Returns a new handle that represents the addition of the receiver and the argument." + | handle | + handle := self class new. + self dice do: [ :each | handle addDie: each ]. + aDieHandle dice do: [ :each | handle addDie: each ]. + ^ handle ``` This definition only works if the method `dice` defined below has been defined ``` DieHandle >> dice + ^ dice ``` Indeed the first expression `self dice do: ` could be rewritten as `dice do: ` because dice is an instance variable of the class `DieHandle`. Now the expression `aDieHandle dice do: ` cannot. Why? Because in Pharo you cannot access the state of another object directly. Here `2 D20` is one handle and `3 D10` another one. The first one cannot access the dice of the second one directly \(while it can accessed its own\). Therefore there is a need to define a message that provide access to the dice. \ No newline at end of file diff --git a/Chapters/DoubleDispatch/DoubleDispatch.md b/Chapters/DoubleDispatch/DoubleDispatch.md new file mode 100644 index 0000000..e83021f --- /dev/null +++ b/Chapters/DoubleDispatch/DoubleDispatch.md @@ -0,0 +1,87 @@ +## Revisiting the Die DSL: a Case for Double Dispatch @ch:doubleDispatch In Chapter *@cha:dsl@*, using the Die DSL we could only sum die handles together as in `2 D20 + 1 D4`. In this new chapter we extend the Die DSL implementation to support the sum of a die with another one or with a die handle \(and vice versa\). One of the challenges is that the message `+` should be able to manage different types of receivers and arguments. The message will have either a die or a die handle as receiver and arguments, so we should manage the following possibilities: die + die handle, die + die, die handle + die handle, and die handle + die. While this extension at first may look trivial, we will take it as a way to explore double dispatch. Double dispatch is a technic that avoids hardcoding type checks and also is able to define incrementally the behavior handling all the possible cases. Indeed double dispatch does not use any explicit conditionals and is the basis of more advanced Design Patterns such as the Visitor. Double dispatch is based on the _Don't ask, tell_ object-oriented principle applied twice. In the case of the `+` message, there is a first dispatch to select the adequate method. Then a second dispatch happens when in this method a new message is sent the _argument_ of the `+` message telling this argument the way the current receiver should be summed. This description is clearly too abstract so we will go over a full example to explain it. ### A little reminder In a previous chapter you implemented a small DSL to add dice and manage die handles. With this DSL, you could create dice and add them to a die handle. Later on you could sum two different die handles and obtain a new one following the "Dungeons and Dragons" ruling book. The following tests show these two behavior: First the dice handle creation and second the sum of die handles. ``` DieHandleTest >> testCreationAdding + | handle | + handle := DieHandle new + addDice: (Dice faces: 6); + addDice: (Dice faces: 10); + yourself. + self assert: handle diceNumber = 2 ``` ``` DieHandleTest >> testSummingWithNiceAPI + | handle | + handle := 2 D20 + 3 D10. + self assert: handle diceNumber = 5 ``` The implementation of `+` was simple since we could only sum die handles together. The method `+` creates a new handle, adds the dice of the receiver and of the argument to the newly created handle and returns it. ``` DieHandle >> + aDieHandle + "Returns a new handle that represents the addition of the receiver and the argument." + | handle | + handle := self class new. + self dice do: [ :each | handle addDie: each ]. + aDieHandle dice do: [ :each | handle addDie: each ]. + ^ handle ``` ### New requirements The first requirement we have is that we want to be able to add two dices together and of course we should obtain a die handle as illustrated by the following test. We want to add two dices together: ``` (Die withFaces: 6) + (Die withFaces: 6) ``` The second requirement is that we want to be able to mix and add a die to a die handle or vice versa as illustrated below: ``` 2 D20 + (Die withFaces: 6) ``` ``` (Die withFaces: 6) + 2 D20 ``` ### Turning requirements as tests Since we are test-infested, we turn such expected behavior into automatically testable expected behavior: we write them as tests. We want to add two dices together: ``` DieTest >> testAddTwoDice + | hd | + hd := (Die withFaces: 6) + (Die withFaces: 6). + self assert: hd dice size = 2. ``` The second requirement is that we want to be able to mix and add a die to a die handle or vice versa as illustrated by the two following tests: ``` DieTest >> testAddingADieAndHandle + | hd | + hd := (Die faces: 6) + + + (DieHandle new + addDie: 6; + yourself). + self assert: hd dice size equals: 2 ``` ``` DieHandleTest >> testAddingAnHandleWithADie + | handle res | + handle := DieHandle new + addDie: (Die faces: 6); + addDie: (Die faces: 10); + yourself. + res := handle + (Die faces: 20). + self assert: res diceNumber equals: 3 ``` Now we are ready to implement such requirements. ### A first implementation A first solution is to explicitly type check the argument to decide what to do. ``` DieHandle >> + aDieOrADieHandle + + ^ (aDieOrADieHandle class = DieHandle) + ifTrue: [ | handle | + handle := self class new. + self dice do: [ :each | handle addDie: each ]. + aDieOrADieHandle dice do: [ :each | handle addDie: each ]. + handle ] + ifFalse: [ | handle | + handle := self class new. + self dice do: [ :each | handle addDie: each ]. + handle addDie: aDie. + handle ] ``` ``` Die >> + aDieOrADieHandle + | selfAsDieHandle | + selfAsDieHandle := DieHandle new addDie: self. + ^ selfAsDieHandle + aDieOrADieHandle ``` The problem of this solution is that it does not scale. As soon as we will have other kinds of arguments we will have to check more and more cases. You may think that this is just a spurious argument. But when you have a model that has around 35 different kinds of nodes as in Pillar the document processing system used to produce this book, this kind of testing logic becomes a nightmare to maintain and extend. ### Sketching double dispatch We can do better. The logic of the solution we have in mind is quite simple but it may be destabilizing at first. Let us sketch it. - When we execute a method we know its receiver and the kind of receiver we have: it can be a die or a die handle. The method dispatch will select the correct method at runtime. Imagine that we have two `+` methods for each class `Die` and `DieHandle`. When a given method `+` will be executed, we will know the exact kind of the receiver. For example, when the method `+` defined on the class `Die` will be executed, we will know that the receiver is a die \(instance of this class\). Similarly when the method `+` defined on the class `DieHandle` will be executed, we will know that the message receiver is a die handle. This is the power of method dispatch: it selects the right method based on the message receiver. - Then the idea is to tell the argument that we want to sum it with that given receiver. It means that each `+` method on the different class has just to send a different message based on the fact that the receiver was a die or a die handle to its argument and let the method dispatch to act once again. After this second dispatch, the correct method will be selected. But let us makes this really concrete. ### Adding two dice Let us step back and start by supporting the sum of two dice. This is rather simple we create and return a die handle to which we add the receiver and the argument. ``` Die >> + aDie + + ^ DieHandle new + addDie: self; + addDie: aDie; yourself ``` Our first test should pass `testAddTwoDice`. But this solution does not support the fact that the argument can be either a die or a die handle. ### Adding a die and a die or a handle Now we want to handle the fact that we can add a die or a die handle to the receiver as illustrated by the test `testAddingADieAndHandle`. ``` DieTest >> testAddingADieAndHandle + | hd | + hd := (Die faces: 6) + + + (DieHandle new + addDie: 6; + yourself). + self assert: hd dice size equals: 2 ``` The previous method `+` is definitively what we want to do when we have two dice. So let us rename it as `sumWithDie:` so that we can invoke it later. ``` Die >> sumWithDie: aDie + + ^ DieHandle new + addDie: self; + addDie: aDie; yourself ``` Now what we can do is to implement `+` as follows. Notice that we named the argument `aDicable` because we want to convey that the argument can be either a die or a die handle. ``` Die >> + aDicable + ^ aDicable sumWithDie: self ``` We tell the argument `aDicable` \(which can be a die or a die handle\) that we want to sum to it a die \(we know that `self` in this method is a `Die` because this is the method of this class that is executed\). When rewritting the `+` method, we switched `self` and `aDicable` to send the new message `sumWithDie:` to the argument \(`aDicable`\). This switch kicks a new method dispatch and we finally have a double dispatch \(one of `+` and one for `sumWithDie:`\). In our two tests `testAddTwoDice` and `testAddingADieAndHandle` we know that the receiver is a die because the method is defined in the class of `Die`. At this point the test `testAddTwoDice` should pass because we are adding two dice as shown in Figure *@figDieDoubleDispatchPartialArgDie@*. ![Summing two dice and be prepared for more.](figures/DieDoubleDispatchPartialArgDie.pdf width=70&label=figDieDoubleDispatchPartialArgDie) ### When the argument is a die handle Now we still have to find a solution for the case where the argument to the message `+` is a die handle. In fact, the argument will receive the message `sumWithDie:`. Therefore if we define a method with that name in the class `DieHandle` it will be executed when the argument of message `+` is a die handle. We know how to sum a die with a die handle: we simply create a new die handle, add all the die of the previous die handle to the new one and add the argument too. So we just have to define the method `sumWithDie:` to the class `DieHandle` implementing this logic. ``` DieHandle >> sumWithDie: aDie + | handle | + handle := self class new. + self dice do: [ :each | handle addDie: each ]. + handle addDie: aDie. + ^ handle ``` Now we are able to sum a die with a die handle as shown in Figure *@figDieDoubleDispatchPartialArgDieHandle@*. The test `testAddingADieAndHandle` should now pass. ![Summing a die and a dicable.](figures/DieDoubleDispatchPartialArgDieHandle.pdf width=90&label=figDieDoubleDispatchPartialArgDieHandle) ### Stepping back You may ask why this is working. We defined two methods `sumWithDie:` one on class `Die` and one on the class `DieHandle` and when the method `+` on class `Die` will send the message `sumWithDie:` to either a die or a die handle, the message dispatch will select the correct method `sumWithDie:` for us as shown in Figure *@figDieDoubleDispatchPartial@*. ![Summing a die and a dicable](figures/DieDoubleDispatchPartial.pdf width=90&label=figDieDoubleDispatchPartial) ### Now a DieHandle as receiver Our solution does not handle the case where the receiver is a die handle. This is what we will address now. Now we are ready to apply the same pattern than before but for the case where the receiver is a die handle. We will just say to the argument of the message `+` that we want to sum it with a _die handle_ this time. We know how to sum two die handles, it is the code we already defined in the previous chapter. We rename the `+` method as `sumWithHandle:` to be able to invoke it while redefining the method `+`. Basically this method creates a new handle, then adds the dice of the receiver and the argument to it and returns the new handle. ``` DieHandle >> sumWithHandle: aDieHandle + | handle | + handle := self class new. + self dice do: [ :each | handle addDie: each ]. + aDieHandle dice do: [ :each | handle addDie: each ]. + ^ handle ``` Now we can define a more powerful version of `+` by simply sending the message `sumWithHandle:` to the **argument** \(aDicable\) of the message `+`. Again we send a message to the argument \(`aDicable`\) to kick in a new message lookup and dispatch for the message `sumWithHandle:`. ``` DieHandle >> + aDicable + ^ aDicable sumWithHandle: self ``` We said that this is version of `+` is more powerful than the one of `sumWithHandle:` because once we will implement the missing method `sumWithHandle:` on the class `Die`, the `+` method will be able to sum die handle with die or die handle. ![Handling all the cases: summing a die/die handle with a die/die handle .](figures/DieDoubleDispatchFull.pdf width=90&label=figDieDoubleDispatchFull) Up until here we did not change much and all the tests adding two die handle should continue to run. ### sumWithHandle: on Die class To get the possibility to sum a die handle with a single die, we just have to define a new method `sumWithHandle:` on the `Die` class. The logic is similar to the one adding one die to one die handle ``` Die >> sumWithHandle: aDieHandle + | handle | + handle := DieHandle new. + aDieHandle dice do: [ :each | handle addDie: each ]. + handle addDie: self + ^ handle ``` Note that we could have sent the message `aDieHandle sumWithDie: self` as body of `sumWithHandle:` definition. Figure *@figDieDoubleDispatchFull@* shows the full set up. We suggest to follow the execution of messages for the different cases to understand that just sending a new message to the argument and relying on method dispatch produces modular conditional execution. Now the following test should pass and we are done. ``` DieHandleTest >> testAddingAnHandleWithADie + | handle res | + handle := DieHandle new + addDie: (Die faces: 6); + addDie: (Die faces: 10); + yourself. + res := handle + (Die faces: 20). + self assert: res diceNumber equals: 3 ``` ### Conclusion When we step back, we see that we applied twice the _Don't ask, tell_ principle: First the message `+` selects the corresponding methods in either `Die` or `DieHandle` classes. Then a more specific message is sent to the argument and the dispatch kicks in again selecting the correct method for the messages `sumWithDie:` or `sumWithHandle:`. In this chapter we presented double dispatch. The idea is to use method dispatch two times. While the resulting design is simple, it is not trivial to deeply understand and it requires time to digest double dispatch. At its core, double dispatch relies on the fact that sending a message to an object selects the correct method -- and sending another message to the message argument will select a new method. Therefore we have effectively selected a method according to the receiver and the argument of a message. Double dispatch is the basis for the Visitor Design pattern that is effective when dealing with complex data structure such as documents, compilers. In such context it is not rare to have more than 30 or 40 different nodes that should be manipulated together to produce specific behavior. \ No newline at end of file diff --git a/Chapters/DoubleDispatch/DoubleDispatchExercise.md b/Chapters/DoubleDispatch/DoubleDispatchExercise.md new file mode 100644 index 0000000..ded3867 --- /dev/null +++ b/Chapters/DoubleDispatch/DoubleDispatchExercise.md @@ -0,0 +1,29 @@ +## Summing and converting money We will now work on one example proposed by A. Bergel and we would like to thank him for it. ``` 1 EUR = 662 CLP (Chilean pesos) ``` ### Requirements ``` TestCase subclass: #CurrencyTest ``` ``` CurrencyTest >> testSum + | clp1 eur1 clp2 eur2 | + clp1 := CLP new value: 3500. + eur1 := EUR new value: 10. + clp2 := CLP new value: 5000. + eur2 := EUR new value: 20. + + self assert: clp1 + clp2 equals: (CLP new value: 8500). + self assert: clp1 + eur1 equals: (CLP new value: 3500 + 6620). + + self assert: eur1 + eur2 equals: (EUR new value: 30). + self assert: eur1 + clp2 equals: (EUR new value: 5000 / 662 + 10). ``` In addition in a second step we will add conversion between Euros and USD. ### Given context You have a class `Currency` to which you can sum other currencyCurrency. ``` Object subclass: #Currency + instVarNames: ‘value’ ``` ``` Currency >> + anotherCurrency + self subclassResponsibility ``` ``` Currency >> printOn: str + super printOn: str. + str nextPut: $<. + str nextPutAll: self value asString. + str nextPut: $>. ``` ### Solution ``` Currency >> sumWithEUR: money + self subclassResponsibility ``` ``` Currency >> sumWithCLP: money + self subclassResponsibility ``` ``` Currency >> = anotherCurrency + ^ self class == anotherCurrency class and: [ self value = anotherCurrency value ] ``` You have two subclasses: ``` Currency subclass: #EUR + +Currency subclass: #CLP ``` ``` EUR >> + anotherCurrency + ^ anotherCurrency sumWithEUR: self ``` ``` EUR >> sumWithEUR: money + ^ EUR new value: self value + money value ``` ``` EUR >> sumWithCLP: money + ^ CLP new value: (self value * 662) + money value ``` ``` CLP >> + anotherCurrency + ^ anotherCurrency sumWithCLP: self ``` ``` CLP >> sumWithEUR: money + ^ EUR new value: (self value / 662) + money value ``` ``` CLP >> sumWithCLP: money + ^ CLP new value: self value + money value ``` \ No newline at end of file diff --git a/Chapters/ESRIASCII/FileOut_emi.md b/Chapters/ESRIASCII/FileOut_emi.md new file mode 100644 index 0000000..2e07150 --- /dev/null +++ b/Chapters/ESRIASCII/FileOut_emi.md @@ -0,0 +1,565 @@ +``` Object subclass: #ESRIParser + instanceVariableNames: '' + classVariableNames: '' + poolDictionaries: '' + category: 'ESRI' ``` ``` ESRIParserTest >> testCutLines + + | lines parser | + parser := ESRIParser new. + parser cutLines: 'NCOLS xxx +NROWS xxx +XLLCORNER xxx +YLLCORNER xxx'. + self assert: parser lines first equals: 'NCOLS xxx'. + self assert: parser lines size equals: 4. ``` ``` ESRIParserTest >> testCutLines + + | lines parser | + parser := ESRIParser new. + parser cutLines: 'NCOLS xxx +NROWS xxx +XLLCORNER xxx +YLLCORNER xxx'. + self assert: parser lines first equals: 'NCOLS xxx'. + self assert: parser lines size equals: 4. ``` ``` ESRIParserTest >> testCutLines + + | parser | + parser := ESRIParser new. + parser cutLines: 'NCOLS xxx +NROWS xxx +XLLCORNER xxx +YLLCORNER xxx'. + self assert: parser lines first equals: 'NCOLS xxx'. + self assert: parser lines size equals: 4. ``` ``` ESRIParser >> cutLines: aString + self shouldBeImplemented. ``` ``` Object subclass: #ESRIParser + instanceVariableNames: 'lines' + classVariableNames: '' + poolDictionaries: '' + category: 'ESRI' ``` ``` ESRIParser >> cutLines: aString + | str | + lines := OrderedCollection new. + str := aString readStream. + [ str atEnd ] whileFalse: + [ lines add: (str upTo: Character cr) ]. + ^ lines + ``` ``` ESRIParser >> lines + ^ lines ``` ``` Object subclass: #ESRIRaster + instanceVariableNames: '' + classVariableNames: '' + poolDictionaries: '' + category: 'ESRI' ``` ``` ESRIParserTest >> testCutLines + + | parser | + parser := ESRIParser new. + parser parser handeline: 'NCOLS 2' + self assert: parser raster numberOfColumns equals: 2 ``` ``` ESRIParserTest >> testHandleNCols + + | parser | + parser := ESRIParser new. + parser parser handeline: 'NCOLS 2' + self assert: parser raster numberOfColumns equals: 2 ``` ``` ESRIParserTest >> testCutLines + + | parser | + parser := ESRIParser new. + parser cutLines: 'NCOLS xxx +NROWS xxx +XLLCORNER xxx +YLLCORNER xxx'. + self assert: parser lines first equals: 'NCOLS xxx'. + self assert: parser lines size equals: 4. ``` ``` ESRIParserTest >> testHandleNCols + + | parser | + parser := ESRIParser new. + parser parser handeline: 'NCOLS 2'. + self assert: parser raster numberOfColumns equals: 2 ``` ``` ESRIParserTest >> testHandleNCols + + | parser | + parser := ESRIParser new. + parser handeline: 'NCOLS 2'. + self assert: parser raster numberOfColumns equals: 2 ``` ``` ESRIParser >> handeline: aString + self shouldBeImplemented. ``` ``` Object subclass: #ESRIParser + instanceVariableNames: 'lines raster' + classVariableNames: '' + poolDictionaries: '' + category: 'ESRI' ``` ``` Object subclass: #ESRIParser + instanceVariableNames: 'lines raster builderMapping' + classVariableNames: '' + poolDictionaries: '' + category: 'ESRI' ``` ``` ESRIParser >> handeline: aString + + | contents | + contents := (aString splitOn: Character space). + raster perform: (builderMapping at: contents first lowercase asSymbol) with: contents second asNumber. + ``` ``` Object subclass: #ESRIRaster + instanceVariableNames: 'numberOfColumns numberOfRows xllcenter yllcenter cellSize noData rows' + classVariableNames: '' + poolDictionaries: '' + category: 'ESRI' ``` ``` ESRIRaster >> numberOfColumns + ^ numberOfColumns ``` ``` ESRIRaster >> numberOfColumns: anObject + numberOfColumns := anObject ``` ``` ESRIRaster >> numberOfRows + ^ numberOfRows ``` ``` ESRIRaster >> numberOfRows: anObject + numberOfRows := anObject ``` ``` ESRIRaster >> xllcenter + ^ xllcenter ``` ``` ESRIRaster >> xllcenter: anObject + xllcenter := anObject ``` ``` ESRIRaster >> yllcenter + ^ yllcenter ``` ``` ESRIRaster >> yllcenter: anObject + yllcenter := anObject ``` ``` ESRIRaster >> cellSize + ^ cellSize ``` ``` ESRIRaster >> cellSize: anObject + cellSize := anObject ``` ``` ESRIRaster >> noData + ^ noData ``` ``` ESRIRaster >> noData: anObject + noData := anObject ``` ``` ESRIRaster >> rows + ^ rows ``` ``` ESRIRaster >> rows: anObject + rows := anObject ``` ``` ESRIParser >> initialize + super initialize. + raster := ESRIRaster new. ``` ``` ESRIParser >> initialize + super initialize. + raster := ESRIRaster new. ``` ``` ESRIParser >> handeline: aString + + | contents | + contents := (aString splitOn: Character space). + raster + perform: (builderMapping at: contents first lowercase asSymbol) + with: contents second asNumber. + ``` ``` ESRIParser >> initialize + super initialize. + raster := ESRIRaster new. + self initializeBuilderMapping ``` ``` ESRIParser >> initializeBuilderMapping ``` ``` ESRIParser >> initializeBuilderMapping + + ``` ``` ESRIParser >> initializeBuilderMapping + + builderMapping := Dictionary new. + builderMapping at: #ncols put: #numberOfColumns:. + builderMapping at: #nrows put: #numberOfRows:. + builderMapping at: #xllcenter put: #xllcenter:. + builderMapping at: #yllcenter put: #xllcenter:. + builderMapping at: #cellsize put: #cellSize:. + builderMapping at: #nodata_value put: #noData:. + ``` ``` ESRIRaster >> initialize + + super initialize. + noData := -9999 ``` ``` ESRIRaster >> initialize + + super initialize. + noData := -9999 ``` ``` ESRIParser >> handeline: aString + + | contents | + contents := (aString splitOn: Character space). + raster + perform: (builderMapping at: contents first asLowercase asSymbol) + with: contents second asNumber. + ``` ``` ESRIParser >> raster + ^ raster ``` ``` ESRIParserTest >> testHandleNRows + + | parser | + parser := ESRIParser new. + parser handeline: 'nrows 2'. + self assert: parser raster numberOfRows equals: 2 ``` ``` ESRIParserTest >> testHandlexllcorner + + | parser | + parser := ESRIParser new. + parser handeline: 'xllcorner 378923'. + self assert: parser raster numberOfRows equals: 378923 ``` ``` Object subclass: #ESRIRaster + instanceVariableNames: 'numberOfColumns numberOfRows xllcenter yllcenter xllcorner yllcorner cellSize noData rows' + classVariableNames: '' + poolDictionaries: '' + category: 'ESRI' ``` ``` ESRIRaster >> xllcorner + ^ xllcorner ``` ``` ESRIRaster >> xllcorner: anObject + xllcorner := anObject ``` ``` ESRIRaster >> yllcorner + ^ yllcorner ``` ``` ESRIRaster >> yllcorner: anObject + yllcorner := anObject ``` ``` ESRIParser >> initializeBuilderMapping + + builderMapping := Dictionary new. + builderMapping at: #ncols put: #numberOfColumns:. + builderMapping at: #nrows put: #numberOfRows:. + builderMapping at: #xllcenter put: #xllcenter:. + builderMapping at: #yllcenter put: #xllcenter:. + builderMapping at: #xllcorner put: #xllcorner:. + builderMapping at: #yllcorner put: #xllcorner:. + builderMapping at: #cellsize put: #cellSize:. + builderMapping at: #nodata_value put: #noData:. + ``` ``` ESRIParserTest >> testHandlexllcorner + + | parser | + parser := ESRIParser new. + parser handeline: 'xllcorner 378923'. + self assert: parser raster xllcorner equals: 378923 ``` ``` ESRIRaster >> initialize + + super initialize. + noData := -9999. ``` ``` ESRIRaster >> initialize + + super initialize. + noData := -9999. + numberOfColumns := 0. + numberOfRows := 0. + xllcenter := 0. + yllcenter := 0. + xllcorner := 0. + yllcorner := 0. + cellSize := 0. + rows := OrderedCollection new. ``` ``` ESRIParserTest >> testHandleCellSize + + | parser | + parser := ESRIParser new. + parser handeline: 'CELLSIZE 30'. + self assert: parser raster cellSize equals: 30 ``` ``` ESRIParser >> cutLines: aString + | str | + str := aString readStream. + [ str atEnd ] whileFalse: + [ self handleline: (str upTo: Character cr) ]. + + ``` ``` ESRIParser >> handleLine: aString + + | contents | + contents := (aString splitOn: Character space). + raster + perform: (builderMapping at: contents first asLowercase asSymbol) + with: contents second asNumber. + ``` ``` ESRIParserTest >> testHandleCellSize + + | parser | + parser := ESRIParser new. + parser handleLine: 'CELLSIZE 30'. + self assert: parser raster cellSize equals: 30 ``` ``` ESRIParserTest >> testHandleNRows + + | parser | + parser := ESRIParser new. + parser handleLine: 'nrows 2'. + self assert: parser raster numberOfRows equals: 2 ``` ``` ESRIParserTest >> testHandlexllcorner + + | parser | + parser := ESRIParser new. + parser handleLine: 'xllcorner 378923'. + self assert: parser raster xllcorner equals: 378923 ``` ``` ESRIParserTest >> testHandleNCols + + | parser | + parser := ESRIParser new. + parser handleLine: 'NCOLS 2'. + self assert: parser raster numberOfColumns equals: 2 ``` ``` ESRIParser >> handeline: aString + + | contents | + contents := (aString splitOn: Character space). + raster + perform: (builderMapping at: contents first asLowercase asSymbol) + with: contents second asNumber. + ``` ``` ESRIParser >> cutLines: aString + | str | + str := aString readStream. + [ str atEnd ] whileFalse: + [ self handleLine: (str upTo: Character cr) ]. + + ``` ``` ESRIParser >> parse: aString + | str | + str := aString readStream. + [ str atEnd ] whileFalse: + [ self handleLine: (str upTo: Character cr) ]. + + ``` ``` ESRIParserTest >> testCutLines + + | parser | + parser := ESRIParser new. + parser parse: 'NCOLS xxx +NROWS xxx +XLLCORNER xxx +YLLCORNER xxx'. + self assert: parser lines first equals: 'NCOLS xxx'. + self assert: parser lines size equals: 4. ``` ``` ESRIParser >> cutLines: aString + | str | + str := aString readStream. + [ str atEnd ] whileFalse: + [ self handleLine: (str upTo: Character cr) ]. + + ``` ``` ESRIParser >> parse: aString + | str | + self initializeRaster. + str := aString readStream. + [ str atEnd ] whileFalse: + [ self handleLine: (str upTo: Character cr) ]. + + ``` ``` ESRIParser >> initializeRaster + raster := ESRIRaster new ``` ``` ESRIParser >> initialize + super initialize. + self initializeRaster. + self initializeBuilderMapping ``` ``` ESRIParserTest >> testParse + + | parser | + parser := ESRIParser new. + parser parse: 'NCOLS 20 +NROWS 30 +XLLCORNER 2 +YLLCORNER 3'. + self assert: parser raster numberOfColumns equals: 20. + self assert: parser lines xllcorner equals: 2. + self assert: parser lines yllcorner equals: 3. ``` ``` ESRIParserTest >> testParse + + | parser | + parser := ESRIParser new. + parser parse: 'NCOLS 20 +NROWS 30 +XLLCORNER 2 +YLLCORNER 3'. + self assert: parser raster numberOfColumns equals: 20. + self assert: parser raster xllcorner equals: 2. + self assert: parser raster yllcorner equals: 3. ``` ``` ESRIParserTest >> testParse + + | parser | + parser := ESRIParser new. + parser parse: 'NCOLS 20 +NROWS 30 +XLLCORNER 2 +YLLCORNER 3'. + self assert: parser raster numberOfColumns equals: 20. + self assert: parser raster numberOfRows equals: 30. + self assert: parser raster xllcorner equals: 2. + self assert: parser raster yllcorner equals: 3. ``` ``` ESRIParserTest >> testCutLines + + | parser | + parser := ESRIParser new. + parser parse: 'NCOLS xxx +NROWS xxx +XLLCORNER xxx +YLLCORNER xxx'. + self assert: parser lines first equals: 'NCOLS xxx'. + self assert: parser lines size equals: 4. ``` ``` ESRIParser >> initializeBuilderMapping + + builderMapping := Dictionary new. + builderMapping at: #ncols put: #numberOfColumns:. + builderMapping at: #nrows put: #numberOfRows:. + builderMapping at: #xllcenter put: #xllcenter:. + builderMapping at: #yllcenter put: #yllcenter:. + builderMapping at: #xllcorner put: #xllcorner:. + builderMapping at: #yllcorner put: #yllcorner:. + builderMapping at: #cellsize put: #cellSize:. + builderMapping at: #nodata_value put: #noData:. + ``` ``` ESRIParserTest >> testHandleNRows + + | parser | + parser := ESRIParser new. + parser handleLine: 'nrows 2'. + self assert: parser raster numberOfRows equals: 2 ``` ``` ESRIParserTest >> testHandlexllcorner + + | parser | + parser := ESRIParser new. + parser handleLine: 'xllcorner 378923'. + self assert: parser raster xllcorner equals: 378923 ``` ``` ESRIParserTest >> testHandleCellSize + + | parser | + parser := ESRIParser new. + parser handleLine: 'CELLSIZE 30'. + self assert: parser raster cellSize equals: 30 ``` ``` ESRIParserTest >> testHandleNCols + + | parser | + parser := ESRIParser new. + parser handleLine: 'NCOLS 2'. + self assert: parser raster numberOfColumns equals: 2 ``` ``` ESRIParserTest >> testParse + + | parser | + parser := ESRIParser new. + parser parse: 'NCOLS 20 +NROWS 30 +XLLCORNER 2 +YLLCORNER 3'. + self assert: parser raster numberOfColumns equals: 20. + self assert: parser raster numberOfRows equals: 30. + self assert: parser raster xllcorner equals: 2. + self assert: parser raster yllcorner equals: 3. ``` ``` ESRIParserTest >> testHandleNRows + + | parser | + parser := ESRIParser new. + parser handleLine: 'nrows 2'. + self assert: parser raster numberOfRows equals: 2 ``` ``` ESRIParserTest >> testHandlexllcorner + + | parser | + parser := ESRIParser new. + parser handleLine: 'xllcorner 378923'. + self assert: parser raster xllcorner equals: 378923 ``` ``` ESRIParserTest >> testHandleCellSize + + | parser | + parser := ESRIParser new. + parser handleLine: 'CELLSIZE 30'. + self assert: parser raster cellSize equals: 30 ``` ``` ESRIParserTest >> testHandleNCols + + | parser | + parser := ESRIParser new. + parser handleLine: 'NCOLS 2'. + self assert: parser raster numberOfColumns equals: 2 ``` ``` ESRIParserTest >> testParse + + | parser | + parser := ESRIParser new. + parser parse: 'NCOLS 20 +NROWS 30 +XLLCORNER 2 +YLLCORNER 3'. + self assert: parser raster numberOfColumns equals: 20. + self assert: parser raster numberOfRows equals: 30. + self assert: parser raster xllcorner equals: 2. + self assert: parser raster yllcorner equals: 3. ``` ``` ESRIParserTest >> testData + + ``` ``` ESRIParserTest >> testDataOneCell + + | parser | + parser := ESRIParser new. + parser handleLine: 'CELLSIZE 30'. + self assert: parser raster cellSize equals: 30 ``` ``` ESRIParserTest >> testData + + ``` ``` ESRIParserTest >> testDataOneCell + + | parser | + parser := ESRIParser new. + parser raster cellSize: 3. + parser raster numberOfColumns: 4. + parser handleLine: '1 1 1 2 2 2 3 3 3 4 4 4 '. + parser raster rows first equals: #(( 1 1 1) (2 2 2 ) (3 3 3) (4 4 4 )) ``` ``` ESRIParser >> handleLine: aString + + | contents isHeader | + contents := (aString splitOn: Character space). + isHeader := contents size = 2 and: [ contents first isNumber not ]. + isHeader + ifTrue: [ + raster + perform: (builderMapping at: contents first asLowercase asSymbol) + with: contents second asNumber. ] + + ``` ``` ESRIParser >> handleHeader: contents + raster + perform: (builderMapping at: contents first asLowercase asSymbol) + with: contents second asNumber ``` ``` ESRIParser >> handleLine: aString + | contents isHeader | + contents := aString splitOn: Character space. + isHeader := contents size = 2 and: [ contents first isNumber not ]. + isHeader + ifTrue: + [ self handleHeader: contents ] ``` ``` ESRIParser >> handleHeader: anArray + "anArray = #(string number)" + raster + perform: (builderMapping at: anArray first asLowercase asSymbol) + with: anArray second asNumber ``` ``` ESRIParser >> handleLine: aString + | contents isHeader | + contents := aString splitOn: Character space. + isHeader := contents size = 2 and: [ contents first isNumber not ]. + isHeader + ifTrue: + [ self handleHeader: contents ] + ifFalse: + [ self handleData: contents ] ``` ``` ESRIParserTest >> testDataOneCell + + | parser | + parser := ESRIParser new. + parser raster cellSize: 3. + parser raster numberOfColumns: 4. + parser handleData: '1 1 1 2 2 2 3 3 3 4 4 4 '. + parser raster rows first equals: #(( 1 1 1) (2 2 2 ) (3 3 3) (4 4 4 )) ``` ``` ESRIParser >> handleData: anArray + "" + raster + perform: (builderMapping at: anArray first asLowercase asSymbol) + with: anArray second asNumber ``` ``` ESRIParser >> handleHeader: anArray + "anArray = #(string numberizedString)" + raster + perform: (builderMapping at: anArray first asLowercase asSymbol) + with: anArray second asNumber ``` ``` ESRIParserTest >> testDataOneCell + + | parser | + parser := ESRIParser new. + parser raster numberOfColumns: 4. + parser handleData: '-9999 -9999 5 2'. + parser raster rows first equals: #(-9999 -9999 5 2) ``` ``` ESRIParser >> handleData: anArray + + | row | + row := Array new: raster numbersOfColumns. + "doing it this way I cut extra values that would overflow the columns numbers. + I could have just iterated on the argument and change its contents too." + 1 to: raster numbersOfColumns do: [ :e | row at: e put: (anArray at: e) asNumber]. + raster addRow: row. ``` ``` ESRIParser >> handleData: anArray + + | row | + row := Array new: raster numberOfColumns. + "doing it this way I cut extra values that would overflow the columns numbers. + I could have just iterated on the argument and change its contents too." + 1 to: raster numberOfColumns do: [ :e | row at: e put: (anArray at: e) asNumber]. + raster addRow: row. ``` ``` ESRIRaster >> addRow: aRow + + rows add: aRow ``` ``` ESRIParserTest >> testDataOneCell + + | parser | + parser := ESRIParser new. + parser raster numberOfColumns: 4. + parser handleData: #('-9999' '-9999' '5' '2'). + parser raster rows first equals: #(-9999 -9999 5 2) ``` ``` ESRIParserTest >> testDataOneCell + + | parser | + parser := ESRIParser new. + parser raster numberOfColumns: 4. + parser handleData: #('-9999' '-9999' '5' '2'). + self assert: parser raster rows first equals: #(-9999 -9999 5 2) ``` ``` ESRIParser >> lines + ^ lines ``` ``` Object subclass: #ESRIParser + instanceVariableNames: 'raster builderMapping' + classVariableNames: '' + poolDictionaries: '' + category: 'ESRI' ``` ``` ESRIParserTest >> testDataOneRow + + | parser | + parser := ESRIParser new. + parser raster numberOfColumns: 4. + parser handleData: #('-9999' '-9999' '5' '2'). + self assert: parser raster rows first equals: #(-9999 -9999 5 2) ``` ``` ESRIParserTest >> testDataOneCell + + | parser | + parser := ESRIParser new. + parser raster numberOfColumns: 4. + parser handleData: #('-9999' '-9999' '5' '2'). + self assert: parser raster rows first equals: #(-9999 -9999 5 2) ``` ``` ESRIParserTest >> testFull + + | parser | + parser := ESRIParser new. + parser parse: 'ncols 4 +nrows 6 +xllcorner 0.0 +yllcorner 0.0 +cellsize 50.0 +NODATA_value -9999 +-9999 -9999 5 2 +-9999 20 100 36 +3 8 35 10 +32 42 50 6 +88 75 27 9 +13 5 1 -9999'. + self assert: parser raster numberOfRows equals: 6. + self assert: parser raster rows size equals: 6. + ``` ``` ESRIRaster >> numberOfRows: anObject + self halt. + numberOfRows := anObject ``` ``` ESRIParserTest >> testFull + + | parser | + parser := ESRIParser new. + parser parse: 'ncols 4 +nrows 6 +xllcorner 0.0 +yllcorner 0.0 +cellsize 50.0 +NODATA_value -9999 +-9999 -9999 5 2 +-9999 20 100 36 +3 8 35 10 +32 42 50 6 +88 75 27 9 +13 5 1 -9999'. + self assert: parser raster numberOfRows equals: 6. + self assert: parser raster rows size equals: 6. + ``` ``` ESRIRaster >> numberOfRows: anObject + numberOfRows := anObject ``` ``` ESRIParserTest >> testFull + + | parser | + parser := ESRIParser new. + parser parse: 'ncols 4 +nrows 6 +xllcorner 0.0 +yllcorner 0.0 +cellsize 50.0 +NODATA_value -9999 +-9999 -9999 5 2 +-9999 20 100 36 +3 8 35 10 +32 42 50 6 +88 75 27 9 +13 5 1 -9999'. + self assert: parser raster numberOfRows equals: 6. + self assert: parser raster rows size equals: 6. + self assert: parser raster rows last equals: #(13 5 1 -9999) + ``` \ No newline at end of file diff --git a/Chapters/ESRIASCII/esriascii.md b/Chapters/ESRIASCII/esriascii.md new file mode 100644 index 0000000..a192780 --- /dev/null +++ b/Chapters/ESRIASCII/esriascii.md @@ -0,0 +1,26 @@ +## A parser for ESRI ASCII Raster ### What is ESRI ASCII The ESRI ASCII raster format can be used to transfer information to or from other cell-based or raster systems. When an existing raster is output to an ESRI ASCII-format raster, the file will begin with header information that defines the properties of the raster such as the cell size, the number of rows and columns, and the coordinates of the origin of the raster. The header information is followed by cell value information specified in space-delimited row-major order, with each row separated by a carriage return. #### Example ASCII raster ``` ncols 480 +nrows 450 +xllcorner 378923 +yllcorner 4072345 +cellsize 30 +nodata_value -32768 +43 2 45 7 3 56 2 5 23 65 34 6 32 54 57 34 2 2 54 6 +35 45 65 34 2 6 78 4 2 6 89 3 2 7 45 23 5 8 4 1 62 ... ``` To convert an ASCII file to a raster, the data must be in this same format. The parameters in the header part of the file must match correctly with the structure of the data values. The basic structure of the ESRI ASCII raster has the header information at the beginning of the file followed by the cell value data: The basic structure of the ESRI ASCII raster has the header information at the beginning of the file followed by the cell value data. The spatial location of the raster is specified by the location of the lower left cell, and either by: The center of the lower left cell ``` NCOLS xxx +NROWS xxx +XLLCENTER xxx +YLLCENTER xxx +CELLSIZE xxx +NODATA_VALUE xxx +row 1 +row 2 +... +row n ``` The lower left corner of the lower left cell ``` NCOLS xxx +NROWS xxx +XLLCORNER xxx +YLLCORNER xxx +CELLSIZE xxx +NODATA_VALUE xxx +row 1 +row 2 +... +row n ``` Row 1 of the data is at the top of the raster, row 2 is just under row 1, and so on. #### Header format The syntax of the header information is a keyword paired with the value of that keyword. These are the definitions of the keywords: Parameter Description Requirements NCOLS Number of cell columns Integer greater than 0. NROWS Number of cell rows Integer greater than 0. XLLCENTER or XLLCORNER X-coordinate of the origin \(by center or lower left corner of the cell\) Match with y-coordinate type. YLLCENTER or YLLCORNER Y-coordinate of the origin \(by center or lower left corner of the cell\) Match with x-coordinate type. CELLSIZE Cell size Greater than 0. NODATA\_VALUE The input values to be NoData in the output raster Optional. Default is -9999. ASCII header information Data format The data component of the ESRI ASCII raster follows the header information. Cell values should be delimited by spaces. No carriage returns are necessary at the end of each row in the raster. The number of columns in the header determines when a new row begins. Row 1 of the data is at the top of the raster, row 2 is just under row 1, and so on. \ No newline at end of file diff --git a/Chapters/Expressions/Expressions.md b/Chapters/Expressions/Expressions.md new file mode 100644 index 0000000..eb2ceba --- /dev/null +++ b/Chapters/Expressions/Expressions.md @@ -0,0 +1,233 @@ +## A little expression interpreter @cha:expressions In this chapter you will build a small mathematical expression interpreter. You will be able to build an expression such as `(3 + 4) * 5` and then ask the interpreter to compute its value. You will revisit tests, classes, messages, methods, and inheritance. You will also see an example of expression trees similar to the ones that are used to manipulate programs. Compilers and code refactorings as offered in Pharo and many modern IDEs work by performing manipulations on these trees. In volume two of this book, we will extend this example to present the Visitor design pattern. ### Starting with constant expressions and a test We start with constant expressions. A constant expression is an expression whose value is always the same, obviously. Let us start by defining a test case class as follows: ``` TestCase subclass: #EConstantTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` We decided to define one test case class per expression class, even if the classes will not contain many tests to begin with. This way makes it easier to define new tests and navigate them. Let us write a first test making sure that when we create a constant, sending it the `evaluate` message returns its value: ``` EConstantTest >> testEvaluate + self assert: (EConstant new value: 5) evaluate equals: 5 ``` When you compile such a test method, the system should prompt you define the class `EConstant`. Let the system drive you. Since we need to store the value of a constant expression, let us add an instance variable `value` to the class definition. At the end you should have the following definition for the class `EConstant`: ``` Object subclass: #EConstant + instanceVariableNames: 'value' + classVariableNames: '' + package: 'Expressions' ``` We define the method `value:` to set the value of the instance variable `value`. It is simply a method that takes one argument and stores it in the `value` instance variable: ``` EConstant >> value: anInteger + value := anInteger ``` You should define the method `evaluate` to return the value of the constant. ``` EConstant >> evaluate + ... Your code ... ``` Your test should now pass. ### Negation Now we can start to work on negation of expressions. Let us write a test. Define a new test case class named `ENegationTest`. ``` TestCase subclass: #ENegationTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` The test `testEvaluate` shows that negation applies to an expression \(here a constant\), and that when we evalute it we get the negated value of the constant: ``` ENegationTest >> testEvaluate + self + assert: + (ENegation new expression: (EConstant new value: 5)) evaluate + equals: -5 ``` Let us execute the test and let the system help us to define the class. A negation defines an instance variable to hold the expression that it negates: ``` Object subclass: #ENegation + instanceVariableNames: 'expression' + classVariableNames: '' + package: 'Expressions' ``` We define a setter method to be able to set the negated expression: ``` ENegation >> expression: anExpression + expression := anExpression ``` Now the `evaluate` method should request the evaluation of the expression and negate it. To negate a number we would suggest sending the message `negated`: ``` ENegation >> evaluate + ... Your code ... ``` ![A flat collection of classes \(with a suspect duplication\).](figures/Expressions.png width=70&label=figExpression) Following the same steps as above, we will now add addition and multiplication of expressions. Then we will make the system a bit easier to manipulate, and revisit its design. ### Adding expression addition To add an expression that supports addition we start by defining a test case class and a simple test: ``` TestCase subclass: #EAdditionTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` A simple test for addition is to make sure that we add correctly two constants: ``` EAdditionTest >> testEvaluate + | ep1 ep2 | + ep1 := EConstant new value: 5. + ep2 := EConstant new value: 3. + self assert: (EAddition new right: ep1; left: ep2) evaluate equals: 8 ``` You should define the class `EAddition`. It will have two instance variables for the two subexpressions it adds: ``` EExpression subclass: #EAddition + instanceVariableNames: 'left right' + classVariableNames: '' + package: 'Expressions' ``` Define the two corresponding setter methods `right:` and `left:`. Now you can define the `evaluate` method for addition: ``` EAddition >> evaluate + ... Your code ... ``` To make sure that our implementation is correct we can also test that we can add negated expressions. It is always good to add tests that cover _different_ scenarios: ``` EAdditionTest >> testEvaluateWithNegation + | ep1 ep2 | + ep1 := ENegation new expression: (EConstant new value: 5). + ep2 := EConstant new value: 3. + self + assert: (EAddition new right: ep1; left: ep2) evaluate + equals: -2 ``` ### Multiplication We do the same for multiplication. Create a test case class named `EMultiplicationTest`, a test, a new class `EMultiplication`, a couple of setter methods and finally a new `evaluate` method. Let us do so quickly and without further comment: ``` TestCase subclass: #EMultiplicationTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` ``` EMultiplicationTest >> testEvaluate + | ep1 ep2 | + ep1 := (EConstant new value: 5). + ep2 := (EConstant new value: 3). + self + assert: + (EMultiplication new right: ep1; left: ep2) evaluate + equals: 15 ``` ``` Object subclass: #EMultiplication + instanceVariableNames: 'left right' + classVariableNames: '' + package: 'Expressions' ``` ``` EMultiplication >> right: anExpression + right := anExpression ``` ``` EMultiplication >> left: anExpression + left := anExpression ``` ``` EMultiplication >> evaluate + ... Your code ... ``` ### Stepping back It is interesting to look at what we have built so far. We have a group of classes whose instances can be combined to create complex expressions. Each expression is, in fact, a tree of subexpressions as shown in Figure *@fig:ExpressionTrees@*. The figure shows two main trees: one for the constant expression `5`, and one for the expression `-5 + 3`. Note that the diagram represents the number 5 as an object because in Pharo even small integers are objects, in the same way that the instances of `EConstant` are objects. ![Expressions are composed of trees.](figures/ExpressionTrees.pdf width=60&label=fig:ExpressionTrees) #### Messages and methods The implementation of the `evaluate` message is worth discussing. What we can see is that _different_ classes understand the same message but execute different methods as shown in Figure *@figExpressionEvaluate@*. !!important A message represents an intent: it represents _what_ should be done. A method represents a specification of _how_ something should be executed. What we see is that sending the message `evaluate` to an expression is making a choice out of the different available implementations of the message. This point is central to object-oriented programming. !!important Sending a message is making a choice from among all the methods with the same name. #### About common superclass Up until now we did not see the need to have an inheritance hierarchy, because there is not much that can be shared or reused. But at this point adding a common superclass would be useful to convey to the reader of the code, or someone who wanted to extend the library, that some concepts in our package, represented as messages, are related, and are variations of a general idea. ![Evaluation: one message and multiple method implementations.](figures/ExpressionsEvaluate.pdf width=80&label=figExpressionEvaluate) #### Design corner: About the addition and multiplication model We could have just one class called, for example, `BinaryOperation`, and it could have an `operator` instance variable, either addition or multiplication. This solution would work but, as usual, having a working program does not mean that its design is any good. In particular having a single class would force us to start to write conditional logic in `evaluate` based on the operator as follows: ``` BinaryExpression >> evaluate + operator = #+ + ifTrue: [ left evaluate + right evaluate ] + ifFalse: [ left evaluate * right evaluate] ``` There are ways in Pharo to make such code more compact but we do not want to use them at this stage. \(For the interested reader, look for the message `perform:` that can execute a method based on its name\). This is annoying because the execution engine itself is made to select methods for us so we want to bypass it using an explicit conditional. In addition when we add power, division, and subtraction we will also have to add more cases to our conditional, making the code less readable and more fragile. As we will see as we read further, one of the key messages of this book is _sending a message is making a choice_ between different implementations. To be able make that choice we should have different implementations, which implies having different classes. !!important Classes represent choices whose methods can be selected in reaction to a message. Having many little classes is better than few large ones. What we could do is to introduce a common superclass between `EAddition` and `EMultiplication` but keep the two subclasses. We will do this later in the chapter. ### Negated as a message Negating an expression is expressed in a verbose way. We have to create explicitly each time an instance of the class `ENegation` as shown in the following snippet: ``` ENegation new expression: (EConstant new value: 5) ``` We propose defining a message `negated` on the expressions themselves that will create such instance of `ENegation`. With this new message, the previous expression can be reduced to: ``` (EConstant new value: 5) negated ``` #### `negated` message for constants Let us write a test to make sure that we capture what we want. ``` EConstantTest >> testNegated + self assert: (EConstant new value: 6) negated evaluate equals: -6 ``` And now we can simply implement it as follows: ``` EConstant >> negated + ^ ENegation new expression: self ``` #### `negated` message for negations ``` ENegationTest >> testNegationNegated + self assert: (EConstant new value: 6) negated negated evaluate equals: 6 ``` ``` ENegation >> negated + ^ ENegation new expression: self ``` This definition is not the best we can do since, in general, it is bad practice to hardcode the class usage inside the class. A better definition would be: ``` ENegation >> negated + ^ self class new expression: self ``` But for now we will keep the first one for the sake of simplicity. #### `negated` message for additions We proceed similarly for additions: ``` EEAdditionTest >> testNegated + | ep1 ep2 | + ep1 := EConstant new value: 5. + ep2 := EConstant new value: 3. + self assert: (EAddition new right: ep1; left: ep2) negated evaluate equals: -8 ``` ``` EAddition >> negated + Your code ``` #### `negated` message for multiplications And finally for multiplications: ``` EMultiplicationTest >> testEvaluateNegated + | ep1 ep2 | + ep1 := EConstant new value: 5. + ep2 := EConstant new value: 3. + self assert: (EMultiplication new right: ep1; left: ep2) negated evaluate equals: -15 ``` ``` EMultiplication >> negated + ... Your code ... ``` Now all your tests should pass, and it is a good moment to save your package. ### Annoying repetition Let us step back and look at what we have. We have a working program - but object-oriented design is meant to bring the code up to a higher standard than merely working! Similar to the situation with the `evaluate` message and methods, we see that the functionality of `negated` is distributed over different classes. What is annoying is that we repeat the exact _same_ code over and over \(see Figure *@fig:ExpressionsNegatedRepeated@*\). This is a poor design because, if we want to change the behavior of negation tomorrow, we will have to change it four times while really only once should be enough. ![Code repetition is a bad smell.](figures/ExpressionsNegatedRepeated.pdf width=70&label=fig:ExpressionsNegatedRepeated) What are the solutions? - We could define another class, `Negator`, that would do the job. Each of our current classes would delegate to it. But it does not really solve our problem since we will have to duplicate all the message sends to call `Negator` instances. - If we define the method `negated` in the superclass \(`Object`\) we only need one definition and it will work. Indeed, when we send the message `negated` to an instance of `EConstant` or `EAddition` the system will not find it locally but in the superclass `Object`. So no need to define it four times but only once in class `Object`. This solution is nice because it reduces the number of similar definitions of the method `negated`, but it is not good because, even if we can add methods to the class `Object`, this is really not a good idea; `Object` is a class shared by the entire system and so we should take care not to add behavior that only makes sense for a single application. - The solution is to introduce a new superclass between our classes and the class `Object`. It will have the same properties of the above solution using `Object`, but without polluting it \(see Figure *@figExpressionHierar@*\). This is what we do in the next section. ### Introducing the Expression class ![Introducing a common superclass.](figures/ExpressionsHierarchy.png width=70&label=figExpressionHierar) Let us introduce a new class to obtain the situation depicted by Figure *@figExpressionHierar@*. We can simply do it by adding a new class: ``` Object subclass: #EExpression + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` and changing all the previous definitions to inherit from `EExpression` instead of `Object`. For example the class `EConstant` is then defined as follows: ``` EExpression subclass: #EConstant + instanceVariableNames: 'value' + classVariableNames: '' + package: 'Expressions' ``` For the first transformation we could also use the class refactor _Insert superclass_. Refactorings are code transformations that do not change the behavior of a program. You can find it under the _Refactorings_ list when you open the context menu on a class. In this case it is only useful for the first change, creating the `EExpression` class. Once the classes `EConstant`, `ENegation`, `EAddition`, and `EMultiplication` are subclasses of `EEXpression`, we should focus on the method `negated`. Now the method refactoring _Push up_ will really help us. - Select the method `negated` in one of the classes - Select the refactoring _Push up_ The system will define the method `negated` in the superclass \(`EExpression`\) and remove all the negated methods in the classes. Now achieved the hierarchy described in Figure *@figExpressionHierar@*. It is a good time to run all your tests again; they should all pass. Now you may also be thinking that we could introduce a new class named `ArithmeticExpression` as a superclass of `EAddition` and `EMultiplication`. Indeed this is something that we could do to factor out common structure and behavior between the two classes. But we will do so later as it would be repetitive to do it now. ### Class creation messages So far to create an instance we have always sent a class the message `new`, followed by a setter method, as shown below: ``` EConstant new value: 5 ``` This is a good opportunity to demonstrate that we can define simple **class** methods that improve the class instance creation interface. While this case is simple, and has few benefits, we think that it makes a nice example. With this in mind we can write the previous example in the following way: ``` EConstant value: 5 ``` Notice the important difference: in the first case the message is sent to the newly created instance while in the second case it is sent to the class itself. We define a class method in the same way we define an instance method. The only difference is that, using the code browser, you need to click on the _Class side_ button to define the method on the class instead of an instance of the class. The class itself is an object; just like its instances, it can also be sent messages and execute methods. #### Better instance creation for constants Define the following method on the class `EConstant`. Notice the definition now uses `EConstant class` and not just `EConstant` to stress that we are defining the class method: % should this be switched around - write test then define class method? ``` EConstant class >> value: anInteger + ^ self new value: anInteger ``` Now define a new test to make sure that our method works correctly: ``` EConstantTest >> testCreationWithClassCreationMessage + self assert: (EConstant value: 5) evaluate equals: 5 ``` #### Better instance creation for negations We do the same for the class `ENegation`: ``` ENegation class >> expression: anExpression + ... Your code ... ``` Of course we write a new test as follows: ``` ENegationTest >> testEvaluateWithClassCreationMessage + self assert: (ENegation expression: (EConstant value: 5)) evaluate equals: -5 ``` #### Better instance creation for additions For addition we shall add a class method `left:right:` that takes two arguments: ``` left: anEExpression right: anEExpression2 + ^ self new left: anEExpression; right: anEExpression2 ``` Since by now we are addicted to tests, we add a new one. ``` EEAdditionTest >> testEvaluateWithClassCreationMessage + | ep1 ep2 | + ep1 := EConstant value: 5. + ep2 := EConstant value: 3. + self assert: (EAddition left: ep1 right: ep2) evaluate equals: 8 ``` #### Better instance creation for multiplications We will let you do the same for multiplication: ``` EMultiplication class >> left: anEExpression right: anEExpression2 + ... Your code ... ``` And another test to check that everything is ok. ``` EMultiplicationTest >> testEvaluateWithClassCreationMessage + | ep1 ep2 | + ep1 := EConstant value: 5. + ep2 := EConstant value: 3. + self assert: (EMultiplication left: ep1 right: ep2) evaluate equals: 15 ``` Run your tests! They should all pass. ### Introducing examples as class messages As you saw when writing the tests, it is quite annoying to repeat all the expressions to generate a given tree. This is especially the case in the tests related to addition and multiplication as we can see below: ``` EEAdditionTest >> testNegated + | ep1 ep2 | + ep1 := EConstant new value: 5. + ep2 := EConstant new value: 3. + self assert: (EAddition new right: ep1; left: ep2) negated evaluate equals: -8 ``` One simple solution is to define some class methods returning typical instances of their classes. To define a class method remember that you should click the _Class side_ button. ``` EConstant class >> constant5 + ^ self new value: 5 ``` ``` EConstant class >> constant3 + ^ self new value: 3 ``` This way we can define the test as follows: ``` EEAdditionTest >> testNegated + | ep1 ep2 | + ep1 := EConstant constant5. + ep2 := EConstant constant3. + self + assert: (EAddition new right: ep1; left: ep2) negated evaluate + equals: -8 ``` The tools in Pharo support such a practice. If we tag a class method with the special annotation ``, the browser will show a little icon on the side and, when we click on it, it will open an inspector on the new instance: ``` EConstant class >> constant3 + + ^ self new value: 3 ``` ``` EConstant class >> constant3 + + ^ self new value: 3 ``` Using the same idea we can define the following class methods to return examples of our classes: ``` EAddition class >> fivePlusThree + + | ep1 ep2 | + ep1 := EConstant new value: 5. + ep2 := EConstant new value: 3. + ^ self new left: ep1 ; right: ep2 ``` ``` EMultiplication class >> fiveTimesThree + + | ep1 ep2 | + ep1 := EConstant constant5. + ep2 := EConstant constant3. + ^ EMultiplication new left: ep1 ; right: ep2 ``` What is nice about these sample instances is that they: - help document the class by providing objects that we can directly use. - support the creation of tests by providing objects that can serve as input for tests. - simplify the writing of tests. So consider using them! ### Printing It is quite annoying that we cannot really see an expression when we inspect it. We would like to get something better than `'an EConstant'` and `'an EAddition'` when we debug our programs. To display this information the debugger and inspector sends the message `printString` to objects, which by default prefix the name of the class with 'an' or 'a'. Let us change this. To do so, we will specialize the method `printOn: aStream`. The message `printOn:` is sent to an object when a program or the system sends the message `printString`. From that perspective `printOn:` is a point of system customization that developers can take advantage of to enhance their programming experience. Note that we do not redefine the method `printString` because it is more complex and `printString` is reused for all the objects in the system. We just have to implement the part that is specific to a given class, in this case `printOn:`. In object-oriented design jargon, `printString` is a _template method_, in the sense that it sets up a context which is shared by other objects, and it hosts _hook methods_ which are program customization points. `printOn:` is such a hook method. The term hook comes from the fact that code of subclasses are invoked in the hook place \(see Figure *@fig:ExpressionsHierarchyPrintOn@*\). The default definition of the method `printOn:` as defined on the class `Object` is as follows: it takes the class name, checks if it starts with a vowel or not, and writes 'a ' or 'an ' to the stream, followed by the class name. This is why we got `'an EConstant'` when we printed a constant expression. ``` Object >> printOn: aStream + "Append to the argument, aStream, a sequence of characters that + identifies the receiver." + | title | + title := self class name. + aStream + nextPutAll: (title first isVowel ifTrue: ['an '] ifFalse: ['a ']); + nextPutAll: title ``` ![printOn: and printString a "hooks and template" in action.](figures/ExpressionsHierarchyPrintOn.pdf width=70&label=fig:ExpressionsHierarchyPrintOn) #### A word about streams A stream is basically a container for a sequence of objects. Once we get a stream we can either read from it or write to it. In our case we will write to the stream. Since the stream passed to `printOn:` is a stream expecting characters, we will add characters or strings \(sequences of characters\) to it. We will use the messages `nextPut: aCharacter` and `nextPutAll: aString`. They add their arguments to the stream at the next and following positions. Don't worry - it is simple enough and we will guide you through it. You can find more information in the streams chapter of _Pharo by Example_ available at [http://books.pharo.org](http://books.pharo.org) #### Printing constant Let us start with a test. Here we check that a constant is printed as its value. ``` EConstantTest >> testPrinting + self assert: (EConstant value: 5) printString equals: '5' ``` The implementation is then simple. We just need to put the value converted as a string to the stream. ``` EConstant >> printOn: aStream + aStream nextPutAll: value printString ``` #### Printing negation For negation we should first put a '`-`' and then recurvisely call the printing process on the negated expression. Remember that sending the message `printString` to an expression should return its string representation. At least until now it will work for constants. ``` (EConstant value: 6) printString +>>> '6' ``` Here is a possible definition: ``` ENegation >> printOn: aStream + aStream nextPutAll: '- '. + aStream nextPutAll: expression printString ``` But, since all the messages are sent to the same object, this method can be rewritten as: ``` ENegation >> printOn: aStream + aStream + nextPutAll: '- '; + nextPutAll: expression printString ``` We can also define it as follows: ``` ENegation >> printOn: aStream + aStream nextPutAll: '- '. + expression printOn: aStream ``` The difference between the first two solutions and the third is as follows: In the solution using `printString`, the system creates two streams: one for each invocation of the message `printString`. One for printing the expression and one for printing the negation. Once the first stream is used the message `printString` converts the stream contents into a string. This new string is then put inside the second stream which, at the end, is yet again converted to a string. So the first two solutions are not really efficient, as they keep converting between string and stream. With the third solution, only one stream is created; each of the methods just put the needed string elements into it. At the end of the process, a single `printString` message converts it into a string. #### Printing addition Now let us write a test for printing addition: ``` EAdditionTest >> testPrinting + self + assert: EAddition fivePlusThree printString + equals: '( 5 + 3 )'. + self + assert: EAddition fivePlusThree negated printString + equals: '- ( 5 + 3 )' ``` To print an `EAddition`: put an open parenthesis, print the left expression, put ` ' + '`, print the right expression and put a closing parenthesis in the stream. ``` EAddition >> printOn: aStream + ... Your code ... ``` #### Printing multiplication And now we do the same for multiplication. ``` EMultiplicationTest >> testPrinting + self + assert: EMultiplication fiveTimesThree negated printString + equals: '- ( 5 * 3 )' ``` ``` EMultiplication >> printOn: aStream + ... Your code ... ``` ### Revisiting the `negated` message for Negation Now we can go back on negating an expression. Our implementation is not nice even if we can negate any expression and get the correct value. If you look at it carefully negating a negation could be better. Printing a negated negation illustrates well the problem: we get two minuses instead of none. ``` (EConstant value: 11) negated +>> '- 11' + +(EConstant value: 11) negated negated +>> '- - 11' ``` One way to fix this would be to change the `printOn:` definition to check if the expression that is negated is an `ENegation`, and not to print the `'-'` if it is. This solution is not ideal as we do not want to write code that explicitly checks if an object is of a given class in a conditional. Remember: we just want to send messages and then let receiving objects perform actions. A good solution is to _specialize_ the message `negated` so that when it is sent to a _negation_ it does not create a new `ENegation` that wraps the receiver, but instead returns the original negated expression. If `negated` is sent to any of our other expressions, the method implemented in `EExpression` will be executed. This way the trees created by a `negated` message can never contain a "negated negation", but the arithmetic values obtained are still correct. To implement this solution, we just need to implement a different version of the method `negated` for `ENegation`. Let's write a test! Since evaluating a single expression or a double negated one gives the same results, we need to define a structural test. This is what we do with the expression `exp negated class = ENegation` below. ``` NegationTest >> testNegatedStructureIsCorrect + | exp | + exp := EConstant constant5. + self assert: exp negated class = ENegation. + self assert: exp negated negated equals: exp. ``` Now you should be able to implement the `negated` method for `ENegation`. ``` ENegation >> negated + ... Your code ... ``` #### Understanding method override When we send a message to an object, the system looks for the corresponding method in the class of the receiver. If it is not defined there, the lookup continues in the superclass of the previous class. By adding a method in the class `ENegation`, we have created the situation shown in Figure *@fig:ExpressionsHierarchyOptimized@*. We say that the message `negated` is _overridden_ in `ENegation` because, for instances of `ENegation`, it hides the method defined in the superclass `EExpression`. It works in following way: - When we send the message `negated` to a constant, the message is not found in the class `EConstant`, so it is looked up in the class `EExpression`, where it is found and its corresponding method is applied to the receiver \(the instance of `EConstant`\). - When we send the message `negated` to a negation, the message is found in the class `ENegation`, and the method is then executed on the receiver, the negation expression. ![The message `negated` is overridden in the class `ENegation`.](figures/ExpressionsHierarchyOptimized.png width=70&label=fig:ExpressionsHierarchyOptimized) ### Introducing the BinaryExpression class @secBinaryExpression Now we will take a moment to improve our first design. We will factor out the behavior of `EAddition` and `EMultiplication`. ``` EExpression subclass: #EBinaryExpression + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` ``` EBinaryExpression subclass: #EAddition + instanceVariableNames: 'left right' + classVariableNames: '' + package: 'Expressions' ``` ``` EBinaryExpression subclass: #EMultiplication + instanceVariableNames: 'left right' + classVariableNames: '' + package: 'Expressions' ``` Now we can use again a refactoring to pull up the instance variables `left` and `right`, as well as the methods `left:` and `right:`. Select the class `EMuplication`, bring up the menu and, under the _Refactoring_ menu, select the instance variable refactoring _Push Up_. Then select the instance variables you want to push up. Now you should get the following class definitions, where the instance variables are defined in the new class and removed from the two subclasses. ``` EExpression subclass: #EBinaryExpression + instanceVariableNames: 'left right' + classVariableNames: '' + package: 'Expressions' ``` ``` EBinaryExpression subclass: #EAddition + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` ``` EBinaryExpression subclass: #EMultiplication + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` We should get a situation similar to the one described by Figure *@figExpressionFactoredState@*. All your tests should still pass. ![Factoring instance variables.](figures/ExpressionsHierarchyStateFactored.png width=70&label=figExpressionFactoredState) Now we can move the methods up in the same way. Select the method `left:` and apply the refactoring _Pull Up Method_. Do the same for the method `right:`. #### Creating a template and hook method Now we can look at the method `printOn:` in additions and multiplications. The definitions are very similar, only the operator changes. We cannot simply copy one of the definitions because it will not work for the other one, but what we can do is to apply the same design idea that worked between `printString` and `printOn:`: we can create a template and hooks that will be specialized in the subclasses. We will use the method `printOn:` as a template with a hook redefined in each subclass. Let define the method `printOn:` in `EBinaryExpression` and remove the other ones from the two classes `EAddition` and `EMultiplication`. ``` EBinaryExpression >> printOn: aStream + aStream nextPutAll: '( '. + left printOn: aStream. + aStream nextPutAll: ' + '. + right printOn: aStream. + aStream nextPutAll: ' )' ``` You you can do the next bit manually or use the _Extract Method_ refactoring, which creates a new method from a part of an existing method and sends a message to the new created method: select the `' + '` inside the method pane and bring the menu and select the Extract Method refactoring, and when prompt give the name `operatorString`. Here is the result you should get: ``` EBinaryExpression >> printOn: aStream + aStream nextPutAll: '( '. + left printOn: aStream. + aStream nextPutAll: self operatorString. + right printOn: aStream. + aStream nextPutAll: ' )' ``` ``` EBinaryExpression >> operatorString + ^ ' + ' ``` Now we can just redefine this method in the `EMultiplication` class to return the required string. ``` EMultiplication >> operatorString + ^ ' * ' ``` ![Factoring instance variables and behavior.](figures/ExpressionsHierarchyStateBehaviorFactored.png width=70&label=figExpressionsHierarchyStateBehaviorFactored) ### What did we learn The introduction of the class `EBinaryExpression` is a rich experience in terms of lessons that we can learn. - Refactorings are more than simple code transformations. Refactorings ensure that their application does not change the behavior of programs. As we have seen, refactorings are powerful operations that really help us perform complex transformations. - We saw that the introduction of a new superclass, and moving instance variables or method to the superclass, does not change the structure or behavior of the subclasses. This is because \(1\) for internal state, the structure of an instance is based on the states described by its class and all its superclasses, \(2\) the lookup starts in the class of the receiver and then looks in its superclasses. - While the method `printOn:` is, in itself, a hook for the method `printString`, it can also play the role of a template method. The method `operatorString` reuses the context created by the `printOn:` method which acts as a template method. In fact, each time we do a `self` send we create a hook method that subclasses can then specialize. ### About hook methods When we introduced `EBinaryExpression` we defined the method `operatorString` as follows: ``` EBinaryExpression >> operatorString + ^ ' + ' ``` ``` EMultiplication >> operatorString + ^ ' * ' ``` And you may wonder if it was worth creating a new method in the superclass so that only one subclass redefines it. #### Creating hooks is always good Firstly, creating a hook is always a good idea because you rarely know how your system will be extended in the future. To show this, we suggest you to add raising to power and division, and see how this can now be done with one class and two methods per new operator. #### Avoid not documenting hooks Secondly, we could have just defined one method `operatorString` in each subclass and no method in the superclass `EBinaryExpression`. It would have worked because `EBinaryExpression` is not meant to have direct instances. Therefore there is no risk that a `printOn:` message is sent to one of its instance and cause an error because no method `operatorString` is found. The code would have looked like the following: ``` EAddition >> operatorString + ^ ' + ' ``` ``` EMultiplication >> operatorString + ^ ' * ' ``` ![Better design: Declaring an abstract method as a way to document a hook method.](figures/ExpressionsHierarchyBinaryAbstract.pdf width=70&label=figExpressionsHierarchyBinaryAbstract) But such a design is not great because, when extending from the class, developers will have to guess by reading the subclass definitions that they should also define a method `operatorString`. A better solution would be to define an _abstract_ method in the superclass: ``` EBinaryExpression >> operatorString + ^ self subclassResponsibility ``` Using the message `subclassResponsibility` declares that a method is abstract and does nothing except force its redefinition in subclasses; a subclass must redefine it explicitly. Using this approach we get the final situation represented in Figure *@figExpressionsHierarchyBinaryAbstract@*. In the solution presented before \(section *@secBinaryExpression@*\) we decided to go for the simplest fix by using one of the operator strings \(`' + '`\) as a default definition for the hook in the superclass `EExpression`. We did this on purpose in order to have this discussion. It was not a good solution since it used a value only useful to a specific subclass in the superclass. It is better to define a default value for a hook in the superclass only when this default value can be used in subclasses, both now and in the future. Note that we should also define `evaluate` as an abstract method in `EExpression` to indicate clearly that each subclass should define an `evaluate`. ### Variables Up until now our mathematical expressions have been rather limited. We have only manipulated constant-based expressions. What we would like is to be able to manipulate variables too. Here is a simple test to show what we mean: we define a variable named `'x'` and then we can later specify that `'x'` should take a given value. Let us create a new test class named `EVariableTest` and define a first test `testValueOfx`. ``` EVariableTest >> testValueOfx + self + assert: ((EVariable new id: #x) evaluateWith: {#x -> 10} asDictionary) + equals: 10. ``` #### Some technical points Let us explain a bit what we are doing with the expression `{#x -> 10} asDictionary`. We should be able to specify that a given variable name is associated with a given value. For this we create a _dictionary_. A dictionary is a data structure for storing _keys_ and their associated _values_. Here a key is the variable name and the value its associated value. Let us present some details first. ##### Dictionaries A dictionary is a data structure containing pairs of keys and values, through which we can access the value of a given key. It can use any object as key and any object as a value. Here we simply use a symbol `#x` since symbols are unique within the system and as such we are sure that we cannot have two keys looking the same but having different values. ``` | d | +d := Dictionary new + at: #x put: 33; + at: #y put: 52; + at: #z put: 98. +d at: y +>>> 52 ``` The previous dictionary can be written more concisely as `{#x -> 33 . #y -> 52 . #z -> 98} asDictionary`. ``` {#x -> 33 . #y -> 52 . #z -> 98} asDictionary at: #y +>>> 52 ``` ##### Dynamic Arrays The expression `{ }` creates a dynamic array. Dynamic arrays execute their expressions and store the resulting values. ``` {2 + 3 . 6 - 2 . 7-2 } +>>> #(5 4 5) ``` ##### Pairs The expression `#x -> 10` creates a pair with a key and a value. ``` | p | +p := #x -> 10. +p key +>>> #x +p value +>>> 10 ``` #### Back to variable expressions If we go a step further, we want to be able to build more complex expressions where, instead of only having constants, we can manipulate variables. This way we will be able to build more advanced behavior such as expression derivations. ``` EExpression subclass: #EVariable + instanceVariableNames: 'id' + classVariableNames: '' + package: 'Expressions' ``` ``` EVariable >> id: aSymbol + id := aSymbol ``` ``` EVariable >> printOn: aStream + aStream nexPutAll: id asString ``` We need to pass bindings \(a binding is a key-value pair\) when evaluating a variable. The value of a variable is the value of the binding whose key is the name of the variable. ``` EVariable >> evaluateWith: aBindingDictionary + ^ aBindingDictionary at: id ``` Your tests should all pass at this point. For more complex expressions \(the ones that interest us\) here are two tests: ``` EVariableTest >> testValueOfxInNegation + self assert: ((EVariable new id: #x) negated + evaluateWith: {#x -> 10} asDictionary) equals: -10 ``` What the second test shows is that we can have an expression and given a different set of bindings the value of the expression will differ. ``` EVariableTest >> testEvaluateXplusY + | ep1 ep2 add | + ep1 := EVariable new id: #x. + ep2 := EVariable new id: #y. + add := EAddition left: ep1 right: ep2. + + self + assert: (add evaluateWith: { #x -> 10 . #y -> 2 } asDictionary) + equals: 12. + self + assert: (add evaluateWith: { #x -> 10 . #y -> 12 } asDictionary) + equals: 22 ``` #### Non working approaches A non-working solution would be to add the following method to `EExpression` ``` EEXpression >> evaluateWith: aDictionary + ^ self evaluate ``` However it does not work for at least the following reasons: - It does not use its argument, and so it only works for trees composed exclusively of constants. - When we send a message `evaluateWith:` to an addition, this message is then turned into an `evaluate` message sent to each of its subexpressions, not an `evaluateWith` message. Alternatively we could add the binding to the variable itself, and only provide an `evaluate` message as follows: ``` (EVariable new id: #x) bindings: { #x -> 10 . #y -> 2 } asDictionary ``` But it defeats the purpose of what a variable is! We should be able to give different values to a variable embedded inside a complex expression. #### The solution: adding `evaluateWith:` The solution is simple but far-reaching: we should change all the implementations and message sends from `evaluate` to `evaluateWith:`! But since this is a tedious task we will use the refactor _Add Parameter_ on our `evaluate` method. Since a refactoring applies itself on the complete system, we need to be a bit cautious because other Pharo classes also implement methods named `evaluate`, and we really do not want to impact them. So here are the steps that we should follow: - Select the Expression package. - Choose _Scoped View_ from the toggle underneath the packages section. - Select one of the implementations of `evaluate`. - Select the _Add argument_ refactoring: type `evaluateWith:` as method selector and proceed when prompted for a default value `Dictionary new`. This last expression is needed because the engine will rewrite all the messages `evaluate` but `evaluateWith: Dictionary new`. - The system is performing many changes. Check that they only affect your classes and accept them all. A test like the following one: ``` EConstant >> testEvaluate + self assert: (EConstant constant5) evaluate equals: 5 ``` is transformed to: ``` EConstant >> testEvaluate + self assert: ((EConstant constant5) evaluateWith: Dictionary new) equals: 5 ``` Your tests should nearly all pass except the ones on variables. Why do they fail? Because the refactoring transformed message sends of `evaluate` to `evaluateWith: Dictionary new`; it did not forward the bindings dictionary to the `evaluateWith` messages sent to the subexpressions. ``` EAddition >> evaluateWith: anObject + ^ (right evaluateWith: Dictionary new) + (left evaluateWith: Dictionary new) ``` This method should be transformed as follows: we should pass the binding to each of the recursive `evaluateWith:` message sends. ``` EAddition >> evaluateWith: anObject + ^ (right evaluateWith: anObject) + (left evaluateWith: anObject) ``` Do the same for the multiplications: ``` EMultiplication >> evaluateWith: anObject + ^ (right evaluateWith: anObject) * (left evaluateWith: anObject) ``` And finally negations: ``` ENegation >> evaluateWith: anObject + ^ (expression evaluateWith: anObject) negated ``` ### Conclusion This little exercise was full of learning potential. Here is a little summary: - A _message_ specifies an intent while a _method_ is a named list of Pharo instructions. We often have one message and many methods with the same name. - _Sending a message_ is finding the method corresponding to the message selector: this selection is based on the class of the object receiving the message. When we look for a method we start in the class of the receiver and go up through the inheritance chain. - Tests are a good way to specify what we want to achieve and then to verify after each change that we did not break something. Tests do not prevent bugs, but they help build confidence in the changes we make. - _Refactorings_ are more than simple code transformations. Usually refactorings sufficiently aware of their context that their application does not change the behavior of the program. As we saw, refactorings are powerful operations that really help us perform complex changes. - We saw that the introduction of a new superclass, and moving instance variables or methods to that superclass, does not change the structure or behavior of the subclasses. This is because \(1\) for instance variables, the internal structure of an instance is based on the state of its class and all of its superclasses, and \(2\) the lookup starts in the class of the receiver and then goes through the superclasses in order. - Each time we send a message we create a potential place \(a _hook_\) for subclasses to get their code definition used in place of the superclass's one \(the _template_\). \ No newline at end of file diff --git a/Chapters/Expressions/ExpressionsSolution.md b/Chapters/Expressions/ExpressionsSolution.md new file mode 100644 index 0000000..e98d28f --- /dev/null +++ b/Chapters/Expressions/ExpressionsSolution.md @@ -0,0 +1,21 @@ +## Expressions solutions @cha:expressionssolutions Here are the possible solutions of the implementation we asked for the Expression Chapter *@cha:expressions@*. ### Evaluate message ``` EConstant >> evaluate + ^ value ``` ``` ENegation >> evaluate + ^ expression evaluate negated ``` ``` EAddition >> evaluate + ^ left evaluate + right evaluate ``` ``` EMultiplication >> evaluate + ^ left evaluate + right evaluate ``` ### Negated message ``` EAddition >>> negated + ^ ENegation new expression: self ``` ``` EMultiplication >>> negated + ^ ENegation new expression: self ``` ### Better class instance creation interface ``` ENegation class >> expression: anExpression + ^ self new expression: anExpression ``` ``` EMultiplication class >> left: anExp right: anExp2 + ^ self new left: anExp ; right: anExp2 ``` ### Printing addition and multiplication ``` EAddition >> printOn: aStream + aStream nextPutAll: '( '. + left printOn: aStream. + aStream nextPutAll: ' + '. + right printOn: aStream. + aStream nextPutAll: ' )' ``` ``` Emultiplication >> printOn: aStream + aStream nextPutAll: '( '. + left printOn: aStream. + aStream nextPutAll: ' * '. + right printOn: aStream. + aStream nextPutAll: ' )' ``` ### Negated negation ``` ENegation >> negated + ^ expression ``` ### evaluateWith: ``` EMultiplication >> evaluateWith: anObject + ^ (right evaluateWith: anObject) + (left evaluateWith: anObject) ``` \ No newline at end of file diff --git a/Chapters/GameCollector/GameCollector.md b/Chapters/GameCollector/GameCollector.md new file mode 100644 index 0000000..fbb8ba8 --- /dev/null +++ b/Chapters/GameCollector/GameCollector.md @@ -0,0 +1 @@ +## Game Collector App ### Defining the model ### Getting Seaside ``` http://files.pharo.org/seaside/Seaside-3.1-portable.zip ``` \ No newline at end of file diff --git a/Chapters/GettingStarted/ChallengingYourself.md b/Chapters/GettingStarted/ChallengingYourself.md new file mode 100644 index 0000000..9a59d6c --- /dev/null +++ b/Chapters/GettingStarted/ChallengingYourself.md @@ -0,0 +1,133 @@ +## Challenge yourself @cha:challenging In Pharo everything is an object and most computation happens by sending _messages_ to objects. In this chapter we propose a list of exercises to challenge you with the syntax. ### Challenge: Message identification For each of the expressions below, fill in the answers: - What is the receiver object? - What is the message selector? - What is/are the argument \(s\)? - What is the result returned by this expression execution? ``` 3 + 4 + + receiver: + selector: + arguments: + result: ``` ``` Date today + + receiver: + selector: + arguments: + result: ``` ``` #('' 'World') at: 1 put: 'Hello' + + receiver: + selector: + arguments: + result: ``` ``` #(1 22 333) at: 2 + + receiver: + selector: + arguments: + result: ``` ``` #(2 33 -4 67) collect: [ :each | each abs ] + + receiver: + selector: + arguments: + result: ``` ``` 25 @ 50 + + receiver: + selector: + arguments: + result: ``` ``` SmallInteger maxVal + + + receiver: + selector: + arguments: + result: ``` ``` #(a b c d e f) includesAll: #(f d b) + + receiver: + selector: + arguments: + result: ``` ``` true | false + + receiver: + selector: + arguments: + result: ``` ``` Point selectors + + receiver: + selector: + arguments: + result: ``` ### Challenge: Literal objects What kind of object does the following literal expressions refer to? It is the same as asking what is the result of sending the `class` message to such expressions. ``` 1.3 + +> ``` ``` #node1 + +> ``` ``` #(2 33 4) + +> ``` ``` 'Hello, Dave' + +> ``` ``` [ :each | each scale: 1.5 ] + +> ``` ``` $A + +> ``` ``` true + +> ``` ``` 1 + +> ``` ### Challenge: Kind of messages Examine the following messages and report if the message is unary, binary or keyword-based. ``` 1 log + +> ``` ``` Browser open + +> ``` ``` 2 raisedTo: 5 + +> ``` ``` 'hello', 'world' + +> ``` ``` 10@20 + +> ``` ``` point1 x + +> ``` ``` point1 distanceFrom: point2 + +> ``` ### Challenge: Results Examine the following expressions. What is the value returned by the execution of the following expressions? ``` 1 + 3 negated + +> ``` ``` 1 + (3 negated) + +> ``` ``` 2 raisedTo: 3 + 2 + +> ``` ``` | anArray | +anArray := #('first' 'second' 'third' 'fourth'). +anArray at: 2 + + +> ``` ``` #(2 3 -10 3) collect: [ :each | each * each] + +> ``` ``` 6 + 4 / 2 + +> ``` ``` 2 negated raisedTo: 3 + 2 + +> ``` ``` #(a b c d e f) includesAll: #(f d b) + +> ``` ### Challenge: unneeded parentheses Putting more parentheses than necessary is a good way to get started. Such practice however leads to less readable expressions. Rewrite the following expressions using the least number of parentheses. ``` x between: (pt1 x) and: (pt2 y) + + + ... ``` ``` ((#(a b c d e f) asSet) intersection: (#(f d b) asSet)) + + + ... ``` ``` (x isZero) + ifTrue: [....] +(x includes: y) + ifTrue: [....] + + + + + + ... ``` ``` (OrderedCollection new) + add: 56; + add: 33; + yourself + + + + + ... ``` ``` ((3 + 4) + (2 * 2) + (2 * 3)) + + + ... ``` ``` (Integer primesUpTo: 64) sum + + + ... ``` ``` ('http://www.pharo.org' asUrl) retrieveContents + + + ... ``` \ No newline at end of file diff --git a/Chapters/GettingStarted/ChallengingYourselfSolution.md b/Chapters/GettingStarted/ChallengingYourselfSolution.md new file mode 100644 index 0000000..fab74b9 --- /dev/null +++ b/Chapters/GettingStarted/ChallengingYourselfSolution.md @@ -0,0 +1,140 @@ +## Solution of challenge yourself ### Challenge: Message identification ``` 3 + 4 + + receiver: 3 + selector: + + arguments: 4 + result: 7 ``` ``` Date today + + receiver: Date + selector: today + arguments: _ + result: The date of today ``` ``` #('' 'World') at: 1 put: 'Hello' + + receiver: #('' 'World') + selector: at:put: + arguments: 1 and 'Hello' + result: #('Hello' 'World') ``` ``` #(1 22 333) at: 2 + + receiver: #(1 22 333) + selector: at: + arguments: 2 + result: 22 ``` ``` #(2 33 -4 67) collect: [ :each | each abs ] + + receiver: #(2 33 -4 67) + selector: collect: + arguments: [ :each | each abs ] + result: #(2 33 4 67) ``` ``` 25 @ 50 + + receiver: 25 + selector: @ + arguments: 50 + result: 25@50 (a point) ``` ``` SmallInteger maxVal + + + receiver: the class SmalltalkInteger + selector: maxVal + arguments: _ + result: returns the largest small integer ``` ``` #(a b c d e f) includesAll: #(f d b) + + receiver: #(a b c d e f) + selector: includesAll: + arguments: #(f d b) + result: true ``` ``` true | false + + receiver: true + selector: | + arguments: false + result: true ``` ``` Point selectors + + receiver: Point + selector: selectors + arguments: _ + result: a long arrays of selectors understood by the class Point ``` ### Challenge: Literal objects What kind of object does the following literal expressions refer to? It is the same as asking what is the result of sending the `class` message to such expressions. ``` 1.3 + +> Float ``` ``` #node1 + +> Symbol ``` ``` #(2 33 4) + +> Array ``` ``` 'Hello, Dave' + +> String ``` ``` [ :each | each scale: 1.5 ] + +> Block ``` ``` $A + +> Character ``` ``` true + +> Boolean ``` ``` 1 + +> SmallInteger ``` ### Challenge: Kind of messages Examine the following messages and report if the message is unary, binary or keyword-based. ``` 1 log + +> Unary ``` ``` Browser open + +> Unary ``` ``` 2 raisedTo: 5 + +> Keyword-based ``` ``` 'hello', 'world' + +> Binary ``` ``` 10@20 + +> Binary ``` ``` point1 x + +> Unary ``` ``` point1 distanceFrom: point2 + +> Keyword-based ``` ### Challenge: Results Examine the following expressions. What is the value returned by the execution of the following expressions? ``` 1 + 3 negated + +> -2 ``` ``` 1 + (3 negated) + +> -2 ``` ``` 2 raisedTo: 3 + 2 + +> 32 ``` ``` | anArray | +anArray := #('first' 'second' 'third' 'fourth'). +anArray at: 2 + + +> 'second' ``` ``` #(2 3 -10 3) collect: [ :each | each * each] + +> #(4 9 100 9) ``` ``` 6 + 4 / 2 + +> 5 ``` ``` 2 negated raisedTo: 3 + 2 + +> -32 ``` ``` #(a b c d e f) includesAll: #(f d b) + +> true ``` ### Challenge: unneeded parentheses Putting more parentheses than necessary is a good way to get started. Such practice however leads to less readable expressions. Rewrite the following expressions using the least number of parentheses. ``` x between: (pt1 x) and: (pt2 y) + +is equivalent to + +x between: pt1 x and: pt2 y ``` ``` ((#(a b c d e f) asSet) intersection: (#(f d b) asSet)) + +is equivalent to + +#(a b c d e f) asSet intersection: #(f d b) asSet ``` ``` (x isZero) + ifTrue: [....] +(x includes: y) + ifTrue: [....] + +is equivalent to + + +x isZero + ifTrue: [....] +(x includes: y) + ifTrue: [....] ``` ``` (OrderedCollection new) + add: 56; + add: 33; + yourself + +is equivalent to + +OrderedCollection new + add: 56; + add: 33; + yourself ``` ``` ((3 + 4) + (2 * 2) + (2 * 3)) + +is equivalent to + +3 + 4 + (2 * 2) + (2 * 3) ``` ``` (Integer primesUpTo: 64) sum + +No changes ``` ``` ('http://www.pharo.org' asUrl) retrieveContents + +is equivalent to + +'http://www.pharo.org' asUrl retrieveContents ``` \ No newline at end of file diff --git a/Chapters/GettingStarted/GettingStarted.md b/Chapters/GettingStarted/GettingStarted.md new file mode 100644 index 0000000..41a32c3 --- /dev/null +++ b/Chapters/GettingStarted/GettingStarted.md @@ -0,0 +1,452 @@ +## Pharo syntax in a nutshell + +@cha:syntax + +In this chapter, we start on a simple path to get you to understand the most important parts of the Pharo syntax: _messages_, _blocks_ and _methods_. +This chapter is freely inspired from Sven van Caeckenberghe's gentle syntax introduction, and I thank him for giving me the permission to reuse his ideas. + +In Pharo, everything is an _object_ and computation happens by sending _messages_ to objects. +Objects are created by sending messages to particular objects named _classes_, which define the structure and behavior of the objects they create, also known as their instances. + + +### Simplicity and elegance of messages + + +Messages are central to computation in Pharo. +While their syntax is quite minimalist, it is very expressive and structures most of the language. + +There are three kinds of messages: unary, binary, and keyword-based. + +#### Sending a message & the receiver + + +Let’s first look at an example of sending a message to an object: +``` +'hello' reversed +``` + + +What this means is that the message `reversed` is sent to the literal string `'hello'`. +In fact, the string `'hello'` is called the _receiver_ of the message; the receiver is always the leftmost part of a message. + +#### Evaluating code and convention for showing results + + +In Pharo, code can be evaluated from anywhere you can type and select text; the system provides various interactive ways to evaluate code and look at the result. +In this book, we will show the result of an expression directly after it, using three chevrons `>>>`. + +Evaluating the piece of code in the previous example yields a new string with the same characters in reverse order: +``` +'hello' reversed +>>> 'olleh' +``` + + +Figure *@fig:Editing@* describes that we edited an expression and executed in with Playground. + +![Executing an expression in Playground.](figures/Editing.png width=70&label=fig:Editing) + + +#### Other messages & return values + + +Our `'hello'` string understands many other messages than `reversed`: +``` +'hello' asUppercase +>>> 'HELLO' +``` + + +As the name implies, the `asUppercase` message returns yet another string `'HELLO'`, which has the same contents as the receiver with each character converted to upper case. +However, messages sent to strings do not always return strings; other kinds of values are possible: +``` +'hello' first +>>> $h + +'hello' size +>>> 5 +``` + + +The message `first` returns the first element of the string: a character. +Literal characters in Pharo syntax are expressed by the dollar sign `$` immediately followed by the character itself. +The message `size` returns the number of elements in the string, which is an integer. + +Strings, characters, integers are objects, because in Pharo _everything_ is an object. +Also, messages _always_ return something, even if the returned value is not used. +One might say that a message can return any value, as long as it's an object. + +#### The selector & unary messages + + +All messages we saw so far have the same receiver, the string `'hello'`; however, the computations were different because the messages differ by their name, or to use the technical term, by their _selector_. +In the syntax of a message, the selector always comes right after the receiver; the message-sending syntax is just the white space in between! + +Those messages are called _unary_ because they involve only one object: their receiver; they do not take any arguments. +Syntactically, the selectors of unary messages must be alphabetic words; the convention to make up longer selectors is to use lower camel case, preferring `asUppercase` over `as_uppercase` or `AsUPPERCASE`. + + + +#### A first keyword-based message + + +Messages often need to pass arguments to the receiver so that it can perform its task; this is what keyword-based messages are for. + +As an example, instead of using `first`, we could use the message `at:`, with an explicit position as a parameter: +``` +'hello' at: 1 +>>>$h +``` + + +The selector `at:` consists of a single keyword that ends with a colon, signifying that it should be followed by an argument; in this case, an integer indicating which element we want to access. +Pharo counts indices starting from 1; therefore the message `at: 2` will access the second element of the receiver. +``` +'hello' at: 2 +>>>$e +``` + + +#### Keyword-based messages with multiple arguments + + +To pass more than one argument, a single message can have as many colon-terminated keywords as necessary, each followed by an argument, like this: +``` +'hello' copyFrom: 1 to: 3 +>>> 'hel' +``` + + +This is one single message, whose selector is really `copyFrom:to:`. +Note how naturally it reads and how, with well-chosen terms, each keyword of the selector documents the argument that follows it. + +In the syntax, you are free to use as much white space as needed between the keywords and the arguments, and like unary messages, the convention is to name each keyword using lower camel case. + +#### Binary messages + + +Binary messages visually differ from the other two kinds because their selectors can only be composed of symbols. +They always expect a single argument, even though they do not end in a colon. + +The main use of binary messages is as arithmetic operations, for instance sending the message `+` to the integer `1`, with `2` as argument: +``` +1 + 2 +``` + + +But there are some other widely-used binary messages outside of arithmetics; for example, the message \(selector\) for string concatenation is a single comma: +``` +'Hello' , ' Pharoers' +>>> 'Hello Pharoers' +``` + + +Here, the receiver is `'Hello'` and `' Pharoers'` is the argument. + +!!coffee The _receiver_ is the object to which a message is sent; it is always first in a message, followed by the _selector_ and arguments. + +!!coffee _Unary messages_ look like words and have no parameters beside their receiver. _Binary messages_ have selectors made of symbols and have one parameter. _Keyword messages_ take a parameter after each colon in their selector. + +!!coffee A message is composed of a receiver, a message name, called its selector and optional arguments. By language abuse, we sometimes use message when in fact we mean the selector of the message. `,` is a message selector and `'a' ,'b'` is a message. + +!!coffee The preferred naming convention for unary and keyword selectors is lower camel case, `likeThis:orThat:`. + + +### Which message is executed first? + + +Simpler messages take precedence over the more complex ones. +This very simple rule determines execution order when messages of different kinds appear in the same expression. +This means that unary messages are evaluated first, then binary messages, and finally keyword-based messages. + +Together, the message syntax and precedence rules keep complex expressions elegant and readable: +``` +'string' asUppercase copyFrom: -1 + 2 to: 6 - 3 +>>> STR +``` + + +When message precedence does not match what you mean, you can force the execution order using parentheses. +In the following example, the expression inside the parentheses is evaluated first; this yields a three-character string `'STR'`, which then receives the message `reversed`. +``` +('string' asUppercase first: 9 / 3) reversed +>>> 'RTS' +``` + + +Finally, note how `copyFrom:to:` and `first:` were sent to the result of `asUppercase`. +All messages are expressions whose result can be the receiver of a subsequent message; this is called _message chaining_. +Unless the precedence rule applies, chained messages execute in reading order, from left to right. +This is quite natural for unary messages: +``` +'abcd' allButFirst reversed +>>> 'dcb' + +'abcd' reversed allButFirst +>>> 'cba' +``` + + +Note however that the chaining rule applies without exception, even to binary messages that look like arithmetic operators: +``` +1 + 2 * 10 +>>> 30 +``` + + +Finally, keyword messages cannot be chained together without using parentheses, since the chain would look like a single big keyword message. + + +### Sending messages to classes + + +Where do new objects come from? +Well, in Pharo, object creation is just another form of computation, so it happens by sending a message to the class itself. +For example, we can ask the class `String` to create an empty string by sending it the message `new`. +``` +String new +>>> '' +``` + + +Classes are really objects that are known by name, so they provide a useful entry point to the system; creating new objects is just a particular use-case. +Some classes understand messages that return specific instances, like the class `Float` that understands the message `pi`. +``` +Float pi +>>> 3.141592653589793 +``` + + +!!coffee The naming convention for class names is upper camel case, `LikeThis`; this is the convention for all non-local names, i.e. shared or global variables. + + +### Local variables and statement sequences + + +Local variables are declared by writing their name between vertical bars; their value can be set using the assignment statement `:=`. +Successive statements are _separated_ using a period, which makes them look like sentences. +``` +| anArray | +anArray := Array new: 3. +anArray at: 1 put: true. +anArray at: 2 put: false. +anArray +>>> #(true false nil) +``` + + +In the code above, a new three-element array is created, and a reference to it is stored in `anArray`. +Then, its first two elements are set using the `at:put:` message, leaving the last element uninitialized; indexing is one-based, like normal humans count. + +The final statement determines the value of the whole sequence; it is shown using the syntax for literal arrays `#( … )`. +The first element is the boolean constant `true`, the second its counterpart `false`. +Uninitialised elements remain `nil`, the undefined object constant. + +!!coffee The first element of a collection is at index `1`. + +!!coffee The naming convention for local variables is lower camel case; variable names often start with an indefinite article, since they refer to otherwise anonymous objects. + +!!coffee There are _only six reserved keywords_, and all are pseudo-variables: the `true`, `false`, and `nil` object constants, and `self`, `super` and `thisContext`, which we talk about later. + + +### About literal objects + + +Most objects in Pharo are created programmatically, by sending a message like `new` to a class. +In addition, the language syntax supports creating certain objects by directly expressing them in the code. +For example the expression `#(true false nil)` is equivalent to the previous snippet using `Array new`. + +In the same way, `$A` is equivalent to `Character codePoint: 65`: +``` +Character codePoint: 65 +>>> $A +``` + + + +### Sending multiple messages to the same object + + +We often need to send multiple messages to the same receiver, in close succession. +For instance, to build a long string without doing too many concatenations, we use a stream: +``` +| aStream | +aStream := (String new: 100) writeStream. +aStream nextPutAll: 'Today, '. +aStream nextPutAll: Date today printString. +aStream contents +>>> 'Today, 28 January 2017' +``` + + +Repeating `aStream` is tedious to read. +To make this flow better, we group the three messages into a _message cascade_, separating them with semicolons, and stating the receiver only once at the beginning: +``` +| aStream | +aStream := (String new: 100) writeStream. +aStream + nextPutAll: 'Today, '; + nextPutAll: Date today printString; + contents +>>> 'Today, 28 January 2017' +``` + + +Like with statement sequences, the cascade as a whole returns the value of its last message. +Here is another example and its cascaded version: +``` +| anArray | +anArray := Array new: 2. +anArray at: 1 put: true. +anArray at: 2 put: false. +anArray +>>> #(true false) +``` + + +``` +(Array new: 2) + at: 1 put: true; + at: 2 put: false; + yourself +>>> #(true false) +``` + + +The three indented messages form a cascade; they are all sent to the same object, the new array. +The last message, `yourself`, is particularly useful to conclude cascades, because it returns the object it is sent to. +This is necessary in this case because the `at:put:` message would return the assigned element, not the array. + + +### Blocks + + +Square brackets `[` and `]` specify _blocks_ \(also known as lexical closures\), pieces of code to be executed later on. + +In the following example, the adder local variable is assigned a one argument block. The code inside the block describes the variables it accepts `:x` and the statements to be executed when it is evaluated `x + 1`. Evaluating a block is done by sending a message, `value:` with an actual object as argument. The argument gets bound to the variable and the block is executed, resulting in 101. + +``` +| adder | +adder := [ :x | x + 1 ]. +adder value: 100 +>>> 101 +adder value: 200 +>>> 201 +``` + + +!!important Technically, blocks are _lexical closures_. Now in a first understanding, they represent kind of anonymous methods that can be stored, passed as arguments, and executed on demand using the messages `value`, `value:`... + +### Control structures + + +Blocks are used to express all control structures, from standard conditionals and loops to the exotic application specific ones, using the normal messaging syntax. For example loops and conditions are all expressed using the message presented previously. There are many loops and conditional in Pharo but they are all using the same principle: a block is passed as argument and the loop definition defines when the block should be executed. + +The message `timesRepeat`: executes multiple time its argument \(a block\). +Here we multiply by two a number 10 times. +``` +n := 1. +10 timesRepeat: [ n := n * 2 ]. +n +>>> 1024 +``` + + +Conditionals are expressed by sending one of the messages `ifTrue:`, +`ifFalse:`, `ifTrue:ifFalse:`, or `ifFalse:ifTrue:` to the result of a boolean expression. + +``` +(17 * 13 > 220) + ifTrue: [ 'bigger' ] + ifFalse: [ 'smaller' ] +>>>'bigger' +``` + + + +The message `do:` allows one to express a loop over a sequence of objects: a block is executed on each of the elements. + +Let us see how we can count the number of character `i` in a given string. On each character we check +if the character is an `$i` and increase the counter value if this is the case. + +``` +| count | +count := 0. +'Fear is the little-death that brings total obliteration' + do: [:c | c == $i ifTrue: [count := count + 1]]. +count +>>> 5 +``` + + +### Methods + + +![Reading or editing a method using a code browser. Topleft pane: list of packages then list of classes then protocols then method lists - middle pane: method definition. Last pane: Quality Assistant.](figures/allSatisfy.png width=95&label=fig:allSatisfy) + +Imagine that we want to check that all the objects in a collection hold a given property. +Here we check that all the numbers in the array are even numbers. +``` +#(2 4 8 16 32) allSatisfy: [ :each | each even ] +>>> true +``` + + +But the following is false because not all the numbers are odd. +``` +#(1 2 3 4 5 6) allSatisfy: [ :each | each odd ] +>>> false +``` + + +The message `allSatisfy:` is one of the many super powerful behaviors implemented in `Collection`. It is called an iterator. + +Methods are edited one by one in a code browser, like the one shown in Figure *@fig:allSatisfy@*. + +The following code is the definition of the method `allSatisfy:` in the class `Collection`. The first line specifies the method name, the selector, with names for all arguments. Comments are surrounded by double quotes. Inside a method, `self` refers to the object itself, the receiver. + +``` +allSatisfy: aBlock + "Evaluate aBlock with the elements of the receiver. + If aBlock returns false for any element return false. + Otherwise return true." + self do: [:each | (aBlock value: each) ifFalse: [^ false]]. + ^ true +``` + + +Let us explain the implementation of this method. Using the message `do:` we iterate over all elements of the collection. For each element we execute block \(a predicate\) that returns a boolean value and act accordingly. As soon as we get a false value, we stop and return an overall false value. If every evaluation gave us true, we passed the whole test and can return `true` as the overall result. + +In a method, the receiver \(`self`\) is the default return value of the whole method. Using a caret \(`^`\) in a method allows to return something else, or to return earlier. + +### Resources + + +This chapter showed you the key syntactic elements. If you want to get a deeper understanding about the syntax please refer to the following mooc videos. The Mooc on Pharo is available at [http://mooc.pharo.org](http://mooc.pharo.org) + +Here are direct pointers to the videos we believe will help you to understand the Pharo syntax and key messages: +- Syntax in a nutshell [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W1/C019SD-W1-S5-v2.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W1/C019SD-W1-S5-v2.mp4) +- Understanding messages [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S1-v3.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S1-v3.mp4) +- Pharo for the Java Programmer [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S2-v3.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S2-v3.mp4) +- Message precedence [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S3-v3.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S3-v3.mp4) +- Sequence and cascade [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S3-v3.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S3-v3.mp4) +- Blocks [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S6-v2.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S6-v2.mp4) +- Loops [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S7-v2.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S7-v2.mp4) +- Booleans and collections [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S8-v2.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S8-v2.mp4) +- Class and Method Definition [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W1/C019SD-W1-S6-v3.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W1/C019SD-W1-S6-v3.mp4) +- Understanding return [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W3/C019SD-W3-S11-v1.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W3/C019SD-W3-S11-v1.mp4) +- Parentheses [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S9-v3.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S9-v3.mp4) +- Yourself [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S10-v3.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W2/C019SD-W2-S10-v3.mp4) +- Variables [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W3/C019SD-W3-S3-v3.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W3/C019SD-W3-S3-v3.mp4) +- Essential collections [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W3/C019SD-W3-S7-v3.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W3/C019SD-W3-S7-v3.mp4) +- Iterators [http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W3/C019SD-W3-S9-v3.mp4](http://rmod-pharo-mooc.lille.inria.fr/MOOC/Videos/W3/C019SD-W3-S9-v3.mp4) + +### Conclusion + + +You have three kinds of messages and the simpler are executed prior to more complex one. Hence unary messages are executed before binary and binary before keyword-based messages. +Blocks are anonymous methods that can be pass around and used to define control structures and loops. + +You now know enough to read 95% of Pharo code. Remember, it is all just messages being sent to objects. + + diff --git a/Chapters/GettingStarted/SyntaxSummary.md b/Chapters/GettingStarted/SyntaxSummary.md new file mode 100644 index 0000000..48b4591 --- /dev/null +++ b/Chapters/GettingStarted/SyntaxSummary.md @@ -0,0 +1,251 @@ +## Syntax summary + +#### Six reserved words only + +| `nil` | the undefined object | +| `true`, `false` | boolean objects | +| `self` | the receiver of the current message | +| `super` | the receiver, in the superclass context | +| `thisContext` | the current invocation on the call stack | + +#### Reserved syntactic constructs + + +| `"comment"` | comment | +| `'string'` | string | +| `#symbol` | unique string | +| `$a`, Character space | the character a and a space | +| 12 2r1100 16rC | twelve \(decimal, binary, hexadecimal\) | +| 3.14 1.2e3 | floating-point numbers | +| `#(abc 123)` | literal array with the symbol `#abc` and the number 123 | +| `{foo . 3 + 2}` | dynamic array built from 2 expressions | +| `#[123 21 255]` | byte array | +| _exp1_. _exp2_ | expression separator \(period\) | +| `;` | message cascade \(semicolon\) | +| var := _expr_ | assignment | +| `^` _expr_ | return a result from a method \(caret\) | +| `[ :e | expr ]` | code block with a parameter | +| `| var1 var2 |` | declaration of two temporary variables | + + + +#### Message Sending + +When we send a message to an object, the message +_receiver_, the method is selected and executed; the message returns an object. Messages syntax mimics natural languages, with a subject, a verb, and complements. + + + +| **Java** | **Pharo** | +| `aColor.setRGB(0.2,0.3,0)` | `aColor r: 0.2 g: 0.3 b: 0 ` | +| `d.put("1", "Chocolate"); ` | `d at: '1' put: 'Chocolate'` | + +#### Three Types of Messages: Unary, Binary, and Keyword + + + +A **unary** message is one with no arguments. + +``` +Array new +>>> anArray +``` + +``` +#(4 2 1) size +>>> 3 +``` + + +`new` is an unary message sent to classes \(classes are objects\). + +A **binary** message takes only one argument and is named by one or more symbol characters from `+`, `-`, `*`, `= `, `<`, `>`, ... + + +``` +3 + 4 +>>> 7 +``` + +``` +'Hello' , ' World' +>>>'Hello World' +``` + + +The `+` message is sent to the object 3 with 4 as + argument. The string 'Hello'receives the message `,` +\(comma\) with `' World'` as the argument. + +A **keyword** message can take one or more +arguments that are inserted in the message name. + +``` +'Pharo' allButFirst: 2 +>>> 'aro' +``` + +``` +3 to: 10 by: 2 +>>> (3 to: 10 by: 2) +``` + + +The second example sends +`to:by:` to 3, with arguments 10 and 2; this +returns an interval containing 3, 5, 7, and 9. + +#### Message Precedence + + +Parentheses `>` unary `>` binary `>` keyword, and finally from +left to right. + +``` +(15 between: 1 and: 2 + 4 * 3) not +>>> false +``` + + +Messages `+` and `*` are sent first, then `between:and:` is sent, and `not`. +The rule suffers no exception: operators are just binary messages with _no notion of mathematical precedence_. 2 + 4 * 3 reads left-to-right and gives 18, not 14! + +#### Cascade: Sending Multiple Messages to the Same Object + +Multiple messages can be sent to the same receiver with ;. +``` +OrderedCollection new + add: #abc; + add: #def; + add: #ghi. +``` + + +The message `new` is sent to `OrderedCollection` which +returns a new collection to which three +add: messages are sent. The value of the whole message cascade +is the value of the last message sent \(here, the symbol +`#ghi`\). To return the receiver of the +message cascade instead \(i.e. the collection\), make sure to send +`yourself` as the last message of the cascade. + +#### Blocks + +Blocks are objects containing code that is executed on demand. They are the basis for control structures like conditionals and loops. + +``` +2 = 2 + ifTrue: [ Error signal: 'Help' ] +``` + + +``` +#('Hello World') + do: [ :e | Transcript show: e ] +``` + + +The first example sends the message `ifTrue:` to the boolean `true` \(computed from +2 = 2\) with a block as argument. Because the boolean is true, +the block is executed and an exception is signaled. The next example +sends the message `do:` to an array. This evaluates the block +once for each element, passing it via the e parameter. As a +result, `Hello World` is printed. + +#### Common Constructs: Conditionals + + +In Java +``` +if (condition) + { action(); } + else { anotherAction();} +``` + + +In Pharo +``` +condition + ifTrue: [ action ] + ifFalse: [ anotherAction ] +``` + + +In Java +``` +while (condition) { action(); + anotherAction(); } +``` + + +In Pharo +``` +[ condition ] whileTrue: [ action. anotherAction ] +``` + + +#### Common Constructs: Loops/Iterators + + +In Java +``` +for(int i=1; i<11; i++){ + System.out.println(i); } +``` + + +In Pharo +``` +1 to: 11 do: [ :i | Transcript show: i ; cr ] +``` + + +In Java +``` +String [] names ={"A", "B", "C"}; +for( String name : names ) { + System.out.print( name ); + System.out.print(","); } +``` + + +In Pharo +``` +| names | +names := #('A' 'B' 'C'). +names do: [ :each | Transcript show: each, ' , ' ] +``` + + +Collections start at 1. Messages `at: index` gives element at index and `at: index put: value` sets element at index to value. + +``` +#(4 2 1) at: 3 +>>> 1 +``` + +``` +#(4 2 1) at: 3 put: 6 +>>>#(4 2 6) +``` + +``` +Set new add: 4; add: 4; yourself +>>> aSet +``` + + +#### Files and Streams + +``` +work := FileSystem disk workingDirectory. +stream := (work / 'foo.txt') writeStream. +stream nextPutAll: 'Hello World'. +stream close. +stream := (work / 'foo.txt') readStream. +stream contents. +>>> 'Hello World' +stream close. +``` + + diff --git a/Chapters/Inheritance/Extending.md b/Chapters/Inheritance/Extending.md new file mode 100644 index 0000000..ca95443 --- /dev/null +++ b/Chapters/Inheritance/Extending.md @@ -0,0 +1,60 @@ +## Extending superclass behavior In the previous chapter we saw that inheritance allows the programmer to factor out and reuse state and behavior. As such inheritance supports the definition of class hierarchy where subclasses specialize behavior of their superclass. We saw that the method look up starts in the class of the receiver and goes up the inheritance chain. We explained that the method found by the lookup is then executed on the receiver of the initial message. Finally we showed that a subclass can specialize and override the behavior of its superclass by defining locally a method with the same name than one method of its superclass. Now inheritance mechanism is even more powerful. With inheritance we can extend locally the behavior of a superclass while reusing it. It is then possible to override a method and in addition to invoke the behavior of the superclass from within the overridden method. We will continue to use and improve the example of file and directories. ### Revisiting printOn: ![`MFFile` and `MFDirectory` contain duplicated logic in `printOn:`.](figures/FileDirectorySuperV0.pdf width=65&label=fig:FileDirectorySuperV0) When we look at the following `printOn:` methods defined in the classes `MFDirectory` and `MFFile` we see that there is code repetition \(as shown in Figure *@fig:FileDirectorySuperV0@*\). Here is the repeated code snippet. ``` parent isNil + ifFalse: [ parent printOn: aStream ]. +aStream << name ``` Here is the definition in the two classes: ``` MFDirectory >> printOn: aStream + parent isNil + ifFalse: [ parent printOn: aStream ]. + aStream << name. + aStream << '/' ``` ``` MFFile >> printOn: aStream + parent isNil + ifFalse: [ parent printOn: aStream ]. + aStream << name ``` It means that if we define a new subclass we will have probably duplicate the same expression. ### Improving the situation To improve the situation above we move up the definition of the `MFFile` class because it also works for `MFElement` \(as shown in Figure *@fig:FileDirectorySuperV1@*\). ``` MFElement >> printOn: aStream + parent isNil + ifFalse: [ parent printOn: aStream ]. + aStream << name ``` ![Improving the logic \(but not fully\).](figures/FileDirectorySuperV1.pdf width=65&label=fig:FileDirectorySuperV1) ``` MFDirectory >> printOn: aStream + parent isNil + ifFalse: [ parent printOn: aStream ]. + aStream << name. + aStream << '/' ``` It means that when we will add a new subclass, this class will at least have a default definition for the `printOn:` method. Now the duplication of logic is not addressed. The same code is duplicated between the class `MFElement` and `MFDirectory`. What we see is that even if the method `printOn:` of class `MFDirectory` is overriding the method of its superclass, we would like to be able to invoke the method of the superclass `MFElement` and to add the behavior `aStream << '/'`. #### Why self does not work! The following definition does not work because it introduces an endless loop. Indeed, since the method lookup starts in the class of the receiver and `self` represents the receiver, it will always find the same method and will not be able to access the method of the superclass. ``` MFDirectory >> printOn: aStream + self printOn: aStream. + aStream << '/' ``` Let us make sure that you are fully with us. Imagine that we have the following expression: ``` | p el1 el2 | +p := MFDirectory new name: 'comics'. +el1 := MFFile new name: 'babar'; contents: 'Babar et Celeste'. +p addElement: el1. +el2 := MFFile new name: 'astroboy'; contents: 'super cool robot'. +p addElement: el2. +String streamContents: [:s | p printOn: s ] ``` 1. We get the message `p printOn: s`. 1. The method `printOn:` is looked up starting in the class of `p`, i.e., `MFDirectory`. 1. The method is found and applied on `p`. 1. The message `self printOn: aStream` is about to be executed. 1. The receiver is `self` and represents `p`. The method `printOn: aStream` is looked up in the class of the receiver, i.e., `MFDirectory`. 1. The same method is found in the class `MFDirectory` and the process restarts at point 3. In summary, we would like that while doing an override, to use the behavior we are overriding. This is possible as we will see in the following section. ![Using `super` to invoke the overridden method `printOn:`.](figures/FileDirectorySuperV2.pdf width=65&label=fig:FileDirectorySuperV2) ### Extending superclass behavior using super Let us implement the solution first and discuss it after. We redefine the method `printOn:` of the class `MFDirectory` as follows and shown in Figure *@fig:FileDirectorySuperV2@*. ``` MFDirectory >> printOn: aStream + super printOn: aStream. + aStream << '/' ``` What we see is that the method `printOn:` does not contain anymore the duplicated expressions with the method `printOn:` of the superclass \(`MFElement`\). Instead by using the special variable `super` the superclass method is invoked. Let us look at it in detail. - The method `MFDirectory >> printOn:` overrides the method `MFElement`: it means that during the lookup \(activated because the message `printOn:` has been sent to instances of `MFDirectory` or future subclasses\), the method `MFElement >> printOn:` cannot be directly found. Indeed when a message is sent to an object, the corresponding method starts in the class of the receiver, therefore the method in `MFDirectory` is found. - Using the special variable `super`, the method lookup is different than with `self`. When the expression `super printOn: aStream` is sent, the lookup does not start anymore from the class of the receiver, it starts _from the superclass of the class containing the expression_ `super printOn:`, i.e. `MFElement`, therefore the method of the superclass is found and executed. - Finally, `super` like `self` represents the receiver of the messages \(for example an instance of the class `MFDirectory`\). Therefore the method is found in the class `MFDirectory` and executed on the original object that first received the message. Let us make sure that you are fully with us. You can compare with the previous execution simulation. ``` | p el1 el2 | +p := MFDirectory new name: 'comics'. +el1 := MFFile new name: 'babar'; contents: 'Babar et Celeste'. +p addElement: el1. +el2 := MFFile new name: 'astroboy'; contents: 'super cool robot'. +p addElement: el2. +String streamContents: [:s | p printOn: s ] ``` 1. We get the message `p printOn: s`. 1. The method `printOn:` is looked up starting in the class of `p`, i.e., `MFDirectory`. 1. The method is found and applied on `p`. 1. The message `super printOn: aStream` is about to be executed. 1. The receiver is `super` and represents `p`. The method `printOn: aStream` is looked up in the superclass of the class containing the expression. The class containing the method is `MFDirectory`, its superclass is then `MFElement`. The lookup starts from `MFElement`. 1. The method is found in the class `MFElement` in the class. 1. The message `parent isNil` is treated on the receiver `p`. What we see is that using `super`, the programmer can extend the superclass behavior and reuse by involving it. !!important `super` is the receiver of the message but when we send a message to `super` the method lookup starts in the superclass of **the class containing** the expression `super`. ### Another example Before explaining with a more theoritical scenario _super_ semantics, we want to show another example that illustrates that super expressions do not have to be the first expression of a method. We can invoke the overridden method at any place inside the overriding method. The example could be more realistic but it shows that super expression does not have to be the first expression of a method. Let us check the two definitions of the two methods `size` in `MFDirectory` and `MFFile`, we see that `name size` is used in both. ``` MFDirectory >> size + | sum | + sum := 0. + files do: [ :each | sum := sum + each size ]. + sum := sum + name size. + sum := sum + 2. + ^ sum ``` ``` MFFile >> size + ^ contents size + name size ``` ![Using `super` to invoke the overridden method `size`.](figures/FileDirectorySuperV3.pdf width=65&label=fig:FileDirectorySuperV3) What we can do is the following: define `size` in the superclass and invoke it using `super` as shown in Figure *@fig:FileDirectorySuperV3@*. Here is then the resulting situation. ``` MFElement >> size + ^ name size ``` ``` MFFile >> size + ^ contents size + super size ``` ``` MFDirectory >> size + | sum | + sum := 0. + files do: [ :each | sum := sum + each size ]. + sum := sum + super size. + sum := sum + 2. + ^ sum ``` What you see is that messages sent to `super` can be used anywhere inside in the overriding method and their results can be used as any other messages. ### Really understanding super To convince you that `self` and `super` points to the same object you can use the message `==` to verify it as follows: ``` MFFile >> funky + ^ super == self ``` ``` MFFile new funky +>>> true ``` !!important `super` is a special variable: `super` \(just like `self`\) is the receiver of the message! Now we take some time to look abstractly at what we presented so far. Imagine a situation as illustrated by Figure *@fig:LookupWithSuperInSuperclassMethodThreeClasses@*. ``` A new bar +>>> ... +C new bar +>>> ... +D new bar +>>> ... ``` #### Solution ![Example to understand `super`.](figures/LookupWithSuperInSuperclassMethodThreeClasses.pdf width=35&label=fig:LookupWithSuperInSuperclassMethodThreeClasses) The solutions are the following ones: ``` A new bar +>>> 10 +C new bar +>>> 20 +D new bar +>>> 100 ``` Let us examine the evaluation of the message `aD bar`: 1. `aD`'s class is `D`. 1. There is no method `bar` in D. 1. The method look up in `C`. The method `bar` is found. 1. The method `bar` of `C` is executed. 1. The message `bar` is sent to `super`. 1. `super` represents `aD` but the lookup starts in the superclass of the class containing the expression `super` so it starts in `B`. 1. The method `bar` is not found in `B`, the lookup continues in `A`. 1. The method `bar` is found in `A` and it is executed on the receiver i.e., `aD`. 1. The message `foo` is sent to `aD`. 1. The method `foo` is found in `D` and executed. It returns 50. 1. Then to finish the execution of method `bar` in `C`, the rest of the expression `+ self foo` should be executed. 1. Message `self foo` returns 50 too, so the result returns 100. !!important The difference between `self` and `super` is that when we send a message to `super` the method lookup starts in the superclass of the class containing the expression `super`. ### Conclusion In this chapter we saw that inheritance also supports the possibilities to override a method and from this overriding method to invoke the overridden one. This is done using the special variable `super`. `super` is the receiver of the message like `self`. The difference is that the method lookup is changed when messages are sent to `super`. The method is looked up in the superclass of the class containing the message sent to `super`. \ No newline at end of file diff --git a/Chapters/Inheritance/Inheritance.md b/Chapters/Inheritance/Inheritance.md new file mode 100644 index 0000000..6cf1505 --- /dev/null +++ b/Chapters/Inheritance/Inheritance.md @@ -0,0 +1,57 @@ +## Inheritance: Incremental definition and behavior reuse @cha:inheritance In Chapter *@cha:objectclass@*, we presented objects and classes. Objects are entities that communicate exclusively by sending and receiving messages. Objects are described by classes that are factories of objects. Classes define behavior and structure of all their instances: All the instances of a class share the same behavior but have their own private state. In this chapter we present the fundamental concept of _inheritance_ that allows a class to reuse and extend the behavior of another class. The idea is that as a programmer we do not want to rewrite from scratch a functionality if another class already offers it. A program specialises the implemented behavior into the new behavior he wants. Inheritance lets us express this concept specialisation. Using inheritance we create trees of concepts where more precise ones refine more abstract and generic ones. Inheritance is based on dynamic method lookup: a method is looked up dynamically within the inheritance tree starting from the class of the receiver. Once this explained we will show that it is possible to get code of a subclass invoked in place of the one of a superclass. To illustrate the important points of inheritance, we revisit the example of Chapter *@cha:objectclass@*. ### Inheritance Object-oriented programming is also based on the _incremental_ definition of abstractions. This _incremental_ definition mechanism is central to support reuse and extension of abstraction. It is called _inheritance_. The idea is that you can define a new abstraction \(a class\) by refining an existing one \(its superclass\). We said that a subclass inherits from a superclass. This way we reuse the code of the superclass instead of rewriting everything from scratch. Class inheritance creates trees of classes. Such trees are based on _generalisation_: a superclass is more generic than its subclasses. A class in such trees can have instances. All the instances share the behavior defined in their class and superclasses. This is within such trees that the system looks up the method corresponding to a message sent to an instance of a class. Inheritance supports code reuse because instance variable and methods defined in a root concept \(class\) are applicable to its refinements \(subclasses\). ![Two classes understanding similar sets of messages and structuring their instances in a similar way.](figures/FileDirectoryV1.pdf width=60&label=FileDirectoryV1) We will use and extend the simple and naive example of files and directories \(seen in Chapter *@cha:objectclass@*\) to illustrate the key aspects of inheritance. While simple, it is enough to show the key properties of inheritance that we want to illustrate: - _incremental definition_: a subclass is defined by expressing the difference to its superclass. A subclass specialises its superclass behavior. - _state reuse_: instances of a subclass have at least the state structure of the superclass. - _behavior reuse_: upon message reception instances, when the class of the receiver does not define a method, methods of the superclasses are executed instead. - _behavior redefinition \(overriding\)_: a subclass may change locally a method definition inherited from its superclass. - _behavior extension_: a subclass often extends the behavior of one of its superclasses by defining new methods and state. - _subclass behavior can be invoked instead of superclass behavior_: behavior defined in a subclass may be executed in place of the one of a superclass. It means that with behavior overriding subclass behavior can be invoked in place of superclass behavior. This is a really important feature of inheritance. ### Improving files/directories example design Let us go back to the example of files and directories introduced in previous chapter. When we look at the situation depicted by Figure *@FileDirectoryV1@* we see that a file is not the same as a directory, even though they share some common state: both have a name and a parent. In addition, they understand some common messages such as `size`, `search:`, `parent:` and `name:`. Remember that `size` and `search:` were not implemented the same way but the messages have indeed the same name. Load the code so that you can get the tests that we asked you to define at the end of chapter *@cha:objectclass@*. ``` Gofer new + smalltalkhubUser: 'StephaneDucasse' project: 'Loop'; + version: 'MyFS2-StephaneDucasse.4'; + load ``` Verify that the tests are all passing \(green\). #### Objectives In the following sections we will take advantage of defining a common superclass and reuse its definition as shown in Figure *@FileDirectoryWithInheritanceObjective@*: It means sharing the maximum structure and behavior between the two classes. We will proceed step by step so that you can see all the steps and understand why this is working. ![Two class taking advantages of inheriting from a common superclass.](figures/FileDirectoryWithInheritanceObjective.pdf width=60&label=FileDirectoryWithInheritanceObjective) ### Transformation strategies Let us define a new class called `MFElement`. ``` Object subclass: #MFElement + instanceVariableNames: '' + classVariableNames: '' + package: 'MyFS2' ``` As you may noticed it, this class is empty. Now we have two possible strategies: - either we make `MFFile` and `MFDirectory` inherit from `MFElement` and step by step we migrate the common state and behavior to the superclass, - or we define new state and behavior in `MFElement` and we remove it from the two classes and when ready we make them inherit from `MFElement`. The second approach may work but it is too risky. Indeed with the first approach we can get a running system after any step we perform: why? Because we first inherit from the new class and move element from the subclasses to the classes and doing so we automatically reuse the superclass behavior and state so our program externally \(for example from the test perspective\) is not changed. With such an approach we can run our tests after any change and control our enhancements. In addition, some of the operations such as moving an instance variable from a class to its superclass are tedious to perform. Here we will perform one operation manually but for the rest of the changes we will use _refactorings_ -- refactorings are program transformations that keep the behavior of the program the same. Let us get started. ### Factoring out state The first step is to make `MFFile` and `MFDirectory` subclasses of `MFElement` as follows: ``` MFElement subclass: #MFFile + instanceVariableNames: 'parent name contents' + classVariableNames: '' + package: 'MyFS2' ``` ``` MFElement subclass: #MFDirectory + instanceVariableNames: 'parent name files' + classVariableNames: '' + package: 'MyFS2' ``` Now you can execute the tests and they will all pass. Now we get ready move some instance variables to the superclass. ![Moving the instance variable name to the superclass.](figures/FileDirectoryInhStateNameOnly.pdf width=50&label=FileDirectoryInhStateNameOnly) #### Moving instance variable name to superclass Since both `MFDirectory` and `MFFile` define that their instances should have a name, we can remove the instance variable `name` from them and uniquely define it in the superclass. We obtain the situation depicted in Figure *@FileDirectoryInhStateNameOnly@*. Let us do that as follows: We remove it first from the `MFFile` and `MFDirectory` classes. ``` MFElement subclass: #MFFile + instanceVariableNames: 'parent contents' + classVariableNames: '' + package: 'MyFS2' ``` ``` MFElement subclass: #MFDirectory + instanceVariableNames: 'parent files' + classVariableNames: '' + package: 'MyFS2' ``` And we add the instance variable `name` to the superclass `MFElement`. ``` Object subclass: #MFElement + instanceVariableNames: 'name' + classVariableNames: '' + package: 'MyFS2' ``` Pay attention that you should be careful and do it in this order else you may be in the situation where name will be defined in the superclass and in one of the subclasses and the system does not allow this and will forbid your action. Again run the tests they should pass again. What the tests execution proves is that we did not change the structure of the instances of `MFFile` and `MFDirectory`. Indeed the structure of an instance is computed from the instance variable lists defined in their class and all the superclasses of that class. #### Moving parent to superclass Since parent is defined in both subclasses, we can do the same for the instance variable parent to obtain the situation shown in Figure *@FileDirectoryInhFullStateOnly@*. You can do it manually, as we did for the instance variable `name`, but you can also use a _refactoring_: Refactorings are powerful program transformations. Using the system browser, bring the menu on the class `MFFile`, select refactoring, then the instance variable category, and finally pull up, as shown in Figure *@Refactoring@*. ![Applying the Pull Up Instance variable refactoring.](figures/Refactoring.png width=60&label=Refactoring) The system will ask you which variable you want to pull up, select `parent`. It will show you the changes that it is about to perform: removing the instance variable from both subclasses and adding one to the superclass. Proceed and the changes will be executed. Your code should be now in the situation depicted in Figure *@FileDirectoryInhFullStateOnly@*. Run the tests and they should again all pass! ![State factored between the two classes and their superclass.](figures/FileDirectoryInhFullStateOnly.pdf width=50&label=FileDirectoryInhFullStateOnly) What is important to see is that if we create a new subclass of `MFElement`, the instances of such class will automatically get `name` and `parent` as instance variables. This is one of the key property of inheritance: you can define a new abstraction structure by extending an existing one. Now we can do the same for the behavior: we will move similar methods in the superclass and remove them from their respective classes. ### Factoring similar methods The methods `parent:`, `parent` and `name:` are the same and defined in the two classes `MFFile` and `MFDirectory`. We will move them to the superclass `MFElement` following a similar process. - First we will remove the method `name:` from the two classes `MFFile` and `MFDirectory` and add one version to the class `MFElement`. You can do this manually. - Second for the method `parent:`, use the method Refactoring _Push Up Method_ that is available from the method list. You can repeat this for the method `parent` too. You should obtain the system described in Figure *@FileDirectoryInhAccessors@*. ![ State and Methods factored out in the superclass.](figures/FileDirectoryInhAccessors.png width=50&label=FileDirectoryInhAccessors) Again run the tests and they should all pass. Why? Let us see what is happening when we send a message. ### Sending a message and method lookup ![When an object receives a message, the corresponding method is looked up in its class and if necessary its superclasses \(Step 1 and 2\). Then the method is executed on the message receiver \(Step 3\).](figures/BasicLookupAndExecution.png width=50&label=BasicLookup) Now it is time to explain what is happening when an object receives a message. In fact this is really simple but extremely powerful. When a message is sent to an object, first the corresponding method is looked up and once the method is found, it is executed on the object that initially received the message. - **Method Lookup**. When an object, the message receiver, receives a message, the method with the same selector than the message is looked up starting from the _class_ of receiver \(See step 1 in Figure *@BasicLookup@*\). When there is no method with the same selector, the look up continues in the superclass of the current class \(See step 2 in Figure *@BasicLookup@*\). - **Method execution**. When a method with the same selector is found in a class, it is returned and executed on the receiver of the message \(See step 3 in Figure *@BasicLookup@*\). Let us look at our example. - When we send the message `astroboy parent: oldcomics`, the method named `parent:` is looked up in the class of the receiver i.e., `MFFile`. This class defines such a method, so it is returned and executed on the file `astroboy`. - The tests pass because when we send the message `parent:` to an instance of the class `MFFile`, the corresponding method is looked up in the class `MFFile`. Since there is no method `parent:` in the class `MFFile`, the lookup continues in the superclass and find it in the class `MFElement` as shown in Figure *@BasicLookup@*. #### Inheritance properties While rather simple, the previous example shows some key properties of inheritance. Inheritance is a mechanism to define abstraction incrementally: a subclass is defined by expressing the difference to its superclass. A subclass refines a general concept into a more specific one. The classes `MFFile` and `MFDirectory` add extra behavior and state to the one defined in the superclass. As such they reuse the state and behavior of their superclass. - _State reuse_: instances of a subclass have at least the structure of their superclasses \(`name` and `parent`\), local state can be added in addition \(`contents` and `files`\). - _Behavior reuse_: when instances of a subclass receive a message, methods of the superclass may be executed. The method `parent:`, `parent`, and `name` are defined in `MFElement` but are executed on instances of the subclasses. Inheritance creates trees of refined concepts. A superclass represents a more abstract concepts and its subclasses make it more and more specific by refining the superclass behavior or extending it by adding new behavior. ### Basic method overrides Since the method lookup starts from the class of the receiver, redefining a method in subclass takes precedence over the method defined in the superclasses. If we define a method with the same name that one of its superclass, this new method will be executed instead of the one in the superclass. This is called a _method override_. This is useful to be able to redefine locally a behavior taking advantage of the specificities of the subclasses. In Figure *@BasicLookup@*, if we add a new method named `parent:` in the class `MFFile`, this method will be executed when the message `parent:` is sent to an instance of the class `File`. We will see later that we can also invoke the method of the superclass while doing a method overrides: it is useful when we want to _extend_ and not just fully change the superclass behavior. But before explaining this, method lookup and execution are systematically applied and we will see in the following sections that it is even more powerful than it may look at first sight. ### self-send messages and lookup create hooks So far we explained how a message is resolved: first the corresponding method is looked up from the class of the receiver and goes up the inheritance tree. Second, the found method is executed on the message receiver. It means that in response to a message, a superclass method may be executed on its subclass instances. This is the same for message sent to `self` \(the receiver of the message\), we invoke the method lookup and `self` may be one subclass instances. There is an important implication: when we have a message sent to `self` in a method, this message may lead to the execution of a method defined in subclasses: because a subclass may override such method. This is why self-sends are also called _hooks_ methods. We will explain carefully this point. #### Example To explain precisely this important point, let us define a new little behavior: to build a better user interface for end-users we add a new message called `describe` that presents in more human friendly way the receiver of the message. Here is a small example: ``` | p el1 el2 | +p := MFDirectory new name: 'comics'. +el1 := MFFile new name: 'babar'; contents: 'Babar et Celeste'. +p addElement: el1. +el2 := MFFile new name: 'astroboy'; contents: 'super cool robot'. +p addElement: el2. +p describe +>>> 'I m a directory named comics' +el1 describe +>>> 'I m a file named babar' ``` #### Describe implementation We implement now the situation described by Figure *@fig:SelfSendLateBinding@*. To implement this behavior, we define the following method `describe` in the class `MFElement`. ``` MFElement >> describe + ^ 'I m a ', self kind, 'named ', name ``` We define the method `kind` to return a default string, even though we do not expect to create instances of this class and subclasses should define their own definition. ``` MFElement >> kind + ^ 'element' ``` In each of the subclasses, we define a corresponding method `kind`, as follows: ``` MFDirectory >> kind + ^ 'directory' ``` ``` MFFile >> kind + ^ 'file' ``` ### Hook/Template explanations Now we are ready to explain what is happening. Let us illustrate the execution of the `(MFFile new name: 'astroboy') describe`. ``` | el1 | +el1 := (MFFile new name: 'astroboy'). +el1 describe +>>> 'I m a file named astroboy' ``` The following steps are executed: - The message `describe` is sent to `el1` an instance of the class `MFFile`. - The method `describe` is looked up in the class `MFFile` \(step 1 in Figure *@fig:SelfSendLateBinding@*\). It is not found, therefore the lookup continues to the superclass. - The lookup looks for the method `describe` in the class `MFElement` \(step 2 in Figure *@fig:SelfSendLateBinding@*\). It is found and executed on the receiver: `el1`. - During the execution of the method `describe`, a new message `kind` using the expression `self kind` is sent \(step 3 in Figure *@fig:SelfSendLateBinding@*\). - The message `kind` is looked up starting from the class of the receiver, `MFFile` \(step 4 in Figure *@fig:SelfSendLateBinding@*\). The method `kind` is found in class `MFFIle` and executed. - The rest of the method `describe` is executed and the resulting string is returned. ![A self-send creates a hook \(`kind`\) that subclasses override. The method ` describe` is called a template because it creates a context.](figures/SelfSendLateBinding.png width=65&label=fig:SelfSendLateBinding) A vocabulary point: the method `describe` is called a _template_ method because it creates a context in which the `kind` methods are executed. The message `kind` is called a hook since subclass implementation may be invoked in this place. This example illustrates the following important points: - Each time we send a message the system chooses the correct method to be executed. - Each time we send a self-send message we create a place where subclass methods may be executed. We create customization points. - Since `self` represents the receiver and that receiver may be an instance from a class that is not loaded at the time the method containing the self-send, we say that `self` is dynamic. It represents the receiver of the message and the lookup for the method to execute starts in the class of the receiver. !!important Messages sent to the receiver \(`self` sends\) define customization points that subclasses can take advantage of to potentially see their code being executed in place of the superclass' one. ### Essence of self and dispatch Now we take some time to look abstractly at what we presented so far. Imagine a situation as illustrated by Figure *@fig:LookupWithSelfInSuperclassMethod@*. The first questions are simple and should be not a problem for you. Without looking at the solutions guess what are the results of the following messages. ``` A new foo +>>> ... +B new foo +>>> ... ``` What is more interesting is the process to get the result of `B new bar`. ``` A new bar +>>> ... +B new bar +>>> ... ``` ![Self semantics abstractly explained.](figures/LookupWithSelfInSuperclassMethod.pdf width=35&label=fig:LookupWithSelfInSuperclassMethod) #### Solutions The solutions are the following ones. ``` A new foo +>>> 10 + +B new foo +>>> 50 + +A new bar +>>> 10 + +B new bar +>>> 50 ``` The most interesting one is `B new bar`. Let us look at the execution of `aB bar` 1. `aB`'s class is `B`. 1. The method look up starts in the class `B`. 1. There is no method `bar` in `B`. 1. The look up continues in `A` and method `bar` is found. 1. The method `bar` is executed on the receiver `aB`. 1. `self` refers to the receiver `aB`. 1. The message `foo` is sent to `self`. 1. The look up of `foo` starts in the `aB`'s class: `B`. 1. The method `foo` is found in class `B` and executed on the receiver `aB`. !!important `self` represents the receiver. Messages sent to it are looked up from the class of the receiver. ### Instance variables vs. messages Reading the previous section you should now understand that there is in fact a difference between accessing directly an instance variable such as `name` in the method below and using an accessor as illustrated in the next redefinition. The two following method definitions are doing the same but have different extensibility potential. ``` MFElement >> describe + ^ 'I m a ', self kind, 'named ', name ``` ``` MFElement >> describe + ^ 'I m a ', self kind, 'named ', self name ``` When you use an accessor, subclasses may redefine the behavior of the accessors. ``` MFElement >> name + ^ name ``` There is no systematic rule that states that we should systematically use accessors instead of instance variable access. What is important when you decide to use an accessor is to use it consistently. Indeed if some parts use direct instance variable access and other parts use accessors, then a programmer extending your code may redefine the accessors in a subclass and his code may not be invoked \(for example if you left places where you directly access an instance variable\). In addition when you decide to use in your class an accessor it is also better that you do so for all the instance variables of the class. Else we may wonder why and uniformity makes the code more understandable. ### Conclusion We presented the concept of inheritance: a subclass is defined as a refinement of a superclass. It reuses the superclass behavior and may extend the structure its instances will have. We show that method lookup happens dynamically and walks the inheritance tree starting from the receiver class. We show that self-sends are creating hooks in the sense that subclass methods may be executed in place of the superclass counterpart. In the following chapter we will see that we can reuse even more methods between all the superclass and its subclasses. \ No newline at end of file diff --git a/Chapters/Inheritance/Inheritance.tex b/Chapters/Inheritance/Inheritance.tex deleted file mode 100755 index 16293cd..0000000 --- a/Chapters/Inheritance/Inheritance.tex +++ /dev/null @@ -1,329 +0,0 @@ -\ifx\wholebook\relax\else -\input{../Common.tex} -\input{../macroes.tex} -\begin{document} -\fi - -\chapter{Inheritance: Reusing and Extending Behavior}\label{cha:inheritance} - -In the previous chapter we presented objects and classes. Objects are entities that communicate by exclusively sending and receiving messages. Objects are described by classes that are factories of objects. All the objects instances of the same class share the same behavior but have its own private state. Class define behavior and structure of all their instances. - -In this Chapter we present the fundamental concept of \index{inheritance} \emph{inheritance} that allows a class to reuse and extend the behavior of another class. The idea is that as a class programmer we do not want to rewrite from scratch a functionality if another class already offers it. We would prefer to express the difference between the implemented behavior and the new behavior we want. This is this difference between classes that inheritance lets us express. - -To illustrate the important points we start by studying the class \ct{Flasher}. Later we define a subclass of the class \ct{Flasher} to illustrate the points we learned. Finally we will explain the issue of instance initialization. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{Studying the \ct{Flasher} class} -The class \ct{Flasher} creates simple graphical objects that flash, \ie\ change color at a regular time interval. We suggest you to first create a flasher executing the following expression \ct{Flasher newStandAlone openInWorld}. - -You can also browse the class \ct{Flasher}. This class defines one instance variable named \ct{onColor} and 5 methods (see Figure~\ref{fig:flasher}). These methods makes sure that the morph is flashing and that we can change the color of the morph. Bring an inspector on the morph (red halo, menu \menu{debug...}, menu item \menu{inspect morph}) and execute the following expression \ct{self onColor: Color green}. - -\begin{figure}[!h] -\begin{center} -\includegraphics[width=3.5cm]{flasher} -\caption{The class \ct{Flasher} and two instances.\label{fig:flasher}} -\end{center} -\end{figure} - - -The class definition (\ref{flasherdef}) shows that the class defines a new instance variable named \ct{onColor}. However the methods defined in the class \ct{Flasher} are not enough to define the complex behavior of a Morph. For example, the fact that we can drag and drop it, change its size, change its border, or even that it is a circle. All this behavior is reused from other classes. The definition shows another key information: the fact that the class \ct{Flasher} is a subclass from \ct{EllipseMorph}. - -\begin{classdef}\label{flasherdef} -\bold{EllipseMorph subclass: #Flasher} instanceVariableNames: '\bold{onColor}' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo' -\end{classdef} - - -Inheritance allows one to define a new class by only specifying the missing delta between a superclass behavior and the expected behavior. A class that inherits from another one is called a \emph{subclass} of the previous one which is called its \emph{superclass} as shown by Figure~\ref{fig:simplesupersubclass}. The class \ct{Ellipse} is the \emph{superclass} of the class \ct{Flasher}. The class \ct{Flasher} reuses and extends the behavior defined by the class \ct{Flasher}. Instances of the class \ct{Ellipse} have a different behavior than the ones of the class \ct{Flasher}. - - -\begin{figure}[h] -\begin{center} -\includegraphics[width=6cm]{simplesupersubclass} -\caption{The class \ct{Ellipse} is the superclass of the class \ct{Flasher}. The class \ct{Flasher} reuses and extends its behavior. Instances of the class \ct{Ellipse} have a different behavior than the ones of the class \ct{Flasher}. \label{fig:simplesupersubclass}} -\end{center} -\end{figure} - - -Inheritance is not limited to only one level as shown by Figure~\ref{fig:supersubclass}. The class \ct{EllipseMorph} is not defined from scratch but inherits from the class \ct{BorderedMorph}. The class \ct{EllipseMorph} defines in particular how the morph is drawn and how to know whether it contains a given point. Again the class \ct{BorderedMorph} which defines morph having a border inherits from the class \ct{Morph}. Finally the class \ct{Morph} which defines all the basic behavior of the graphical object inherits from the class \ct{Object}. The class \ct{Object} defines all the basic behavior that all the objects have to know. The class \ct{Object} is the root of the inheritance tree in \st. - -As Figure~\ref{fig:supersubclass} shows it, while a class has only one superclass, a class may have multiple subclasses. Here \ct{BorderedMorph} has two subclasses \ct{EllipseMorph} and \ct{EyeMorph}. - -\begin{figure}[ht] -\begin{center} -\includegraphics[width=5cm]{supersubclass} -\caption{The complete inheritance hierarchy of \ct{Flasher}. The class \ct{Object} is root to the inheritance hierarchy.\label{fig:supersubclass}} -\end{center} -\end{figure} - -You can open an hierarchical browser to see the inheritance hierarchy. For this select the class \ct{Flasher} then bring the menu and choose the menu \menu{browse hierarchy}. You should get the browser shown in Figure~\ref{fig:flasherInheritanceBrowser}. This browser shows all the superclasses of the class \ct{Flasher}. - -\begin{figure}[h] -\begin{center} -\includegraphics[width=12cm]{flasherInheritanceBrowser} -\caption{A hierarchy browser opened on the \ct{Flasher} class. You can browse its superclass by clicking on them.\label{fig:flasherInheritanceBrowser}} -\end{center} -\end{figure} - - -\largecadre{A class inherits behavior and state from superclasses. It just have to define new and different behavior.} - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{Different Kinds of Incremental Definition} -With inheritance a class is not defined from scratch but in the context of its superclass. While defining a class we can add state, define new behavior, extend inherited behavior, or change inherited behavior of the superclass. We now look at each scenario on the class \ct{Flasher}. - -\paragraph{Adding State.} A subclass may need some extra state to implement a new behavior. It defines new instance variables. The new class then has the instance variables of its superclass and the new ones. The class \ct{Flasher} adds the \ct{onColor} instance variable that represents the flasher color when it is on. The idea is that the \ct{onColor} instance variable acts as a binary flag telling at each step whether the \ct{onColor} is the color of the morph or not (See method~\ref{mth:Flasherstep2}). - - -There are some simple rules that control the addition of instance variables. The name of an instance variable should be unique among the superclass chain, \ie\ if a class defines a given instance variable \ct{bounds}, a subclass cannot define another instance variable with the same name. - -The System Browser allows one to see all the instance variables defined in an inheritance branch. Select the class \ct{Flasher}, then select the menu item \menu{show hierarchy}. You should get the information displayed in Figure~\ref{fig:flasherInheritance}, where each line represents a class and the list of instance variables is shown in parentheses. -We see that the class \ct{BorderedMorph} defines two instance variables: \ct{borderWidth} and \ct{borderColor}, that the class \ct{EllipseMorph} does not define new instance variable and that the class \ct{Flasher} defines one instance variable \ct{onColor}. - -\begin{figure}[h] -\begin{center} -\includegraphics[width=10cm]{flasherInheritance} -\caption{.\label{fig:flasherInheritance}} -\end{center} -\end{figure} - -Note that we can also ask the class itself for all its instance variable names as follow: - -\begin{scriptwithtitle}{Asking a class all its instance variables} - \ct{Flasher} allInsVarNames. -returns -#('bounds' 'owner' 'submorphs' 'fullBounds' 'color' 'extension' -'borderWidth' 'borderColor' 'onColor') -\end{scriptwithtitle} - - -\paragraph{Adding Behavior.} The class \ct{Flasher} adds flashing behavior to the \ct{EllipseMorph} class. The methods \ct{onColor}, \ct{onColor:} are only defined for the the \ct{Flasher}. Instances of the class \ct{Flasher} understand it while instances of the class \ct{EllipseMorph} do not understand these messages for reasons that we explain below. - -\paragraph{Changing Behavior.} By defining a method in a subclass we can change the behavior inherited from a superclass. For example the method \ct{stepTime} has the responsibility is to return the number of milliseconds between to call to the method \ct{step}. The class \ct{Flasher} redefines the method \ct{stepTime} inherited from the class \ct{Morph}. Here the redefinition is simple it just returns a different amount of time that is used as basis to make the morph flash. In object-oriented programming jargon we called this practice \emph{overriding} as the new method hides the inherited one. - -\begin{method} -Flasher>>stepTime "Answer the desired time between steps, in milliseconds." ^ 500 -\end{method} - -\paragraph{Extending Behavior.} Sometimes we just want to extend the inherited behavior. We define a method that is overriding an inherited method as shown in the previous point but we want to also invoke the hidden method. -This way the overridden method is executed and we are able to define extra behavior. Invoking an overriden method is done by using not \ct{self} but \ct{super} to send a message. \ct{super} as \ct{self} refers to the receiving object but it makes the method lookup does not start in the receiver class but in its direct superclass as explained in Section~\ref{sec:lookup}. - - The method \ct{step} is invoked regularly by the \sq environment and is used to perform morph animation. The method \ct{step} \ref{mth:Flasherstep2} of the class \ct{Flasher} -is responsible of making the morph flash. The expression \ct{super step} invokes the \ct{step} behavior defined in the \ct{Flasher}'s superclasses then make the color flashes by comparing whether the color should change. When the onColor the same as the morph's color, then the color is changed to be slightly darker, when the colors are not the same then the morph takes back its original color. - -\begin{method}\label{mth:Flasherstep2} -Flasher>>\bold{step} "Perform my standard periodic action" \bold{super step.} self color = self onColor ifTrue: [self color: (onColor alphaMixed: 0.5 with: Color black)] ifFalse: [self color: onColor] - \end{method} - -\largecadre{A class can add behavior, state in addition to the ones it inherits. It can also redefine or extend inherited behavior.} - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{Method Inheritance and Method Lookup}\label{sec:lookup} -Besides inheriting the state of its superclasses a subclass also inherits its behavior and can modify it. To understand how behavior inheritance works we have to explain what happens when an object receives a message. When an object receives a message the corresponding method should be found. This is phase is called the \emph{method lookup}\index{method lookup}. Let us look at the way method lookup works. - -When an object receives a message the \index{method lookup} lookup for a method with the same selector start in the receiver class. - -\begin{figure}[ht] -\begin{center} -\includegraphics[width=5cm]{lookupEssence} \caption{When a message is sent, a method is looked up in the \emph{class} of the receiver.} -\end{center} \end{figure} - - -If the method is found in the class it is executed (for example \ct{onColor:} in Figure~\ref{fig:basicLookup1} left), else the lookup continue in the superclasses (see \ct{containsPoint:} in Figure~\ref{fig:basicLookup1} left). When the method is found it is executed. If the lookup fails on the class \obj, the root of the inheritance, this means that the method is not defined for this receiver and an error is generated (see Figure~\ref{fig:basicLookup1} right) and a dialog box pops up and ask us if we want to open a debugger. - -\paragraph{Examples.} -As shown in Figure~\ref{fig:basicLookup1} when an instance of the class \ct{Flasher} receives the message \ct{onColor: Color green}, the lookup of the method that should be executed starts in the class of the receiver, here \ct{Flasher}. The method is defined in this class so it is executed. -When an instance of the class \ct{Flasher} receives the message \ct{containsPoint: 10@10}, the lookup of the method \ct{containsPoint:} starts in the class of the receiver, the class \ct{Flasher}. The method is not defined there so it continues in the superclass of \ct{Flasher}, \ie\ the class \ct{EllipseMorph}, where the method \ct{containsPoint:} is defined. - -\begin{figure}[ht] -\centerline{\includegraphics[width=6cm]{basicLookup1}\includegraphics[width=6cm]{basicLookup2}} \caption{\textbf{Left:} An instance of the class \ct{Flasher} receives the message \ct{onColor: Color green}. The lookup of the method that should be executed starts in the class of the receiver, here \ct{Flasher}. The method is defined in this class so it is executed. An instance of the class \ct{Flasher} receives the message \ct{containsPoint:}. The lookup of the method that should be executed starts in the class of the receiver, here \ct{EllipseMorph}. The method is not defined in this class so it is lookep up in the superclass \ct{EllipseMorph}. -\textbf{Right:} An instance of the class \ct{Flasher} receives the message \ct{foo}. This method is not defined in the class \ct{Flasher} nor in any of its superclasses, therefore an error is raised.\label{fig:basicLookup1}} \end{figure} - -\paragraph{Methods Added on Subclasses.} -The method lookup shows the way a method is found when a message is sent to an object. A direct consequence is that while a subclass inherits the behavior of its superclasses the opposite is not true: This is not because a subclass defines a new method that an instance of its superclasses can understand it. The methods added on a classes are only available for the instances of this class or its subclasses not its superclasses. As shown by Figure~\ref{fig:basicLookup3} when an instance of the class \ct{EllipseMorph} receives the message \ct{onColor:}, the method lookup starts in the class of the receiver \ct{EllipseMorph}. The method is not defined in this class so it continues in the superclasses and eventually generates an error. - - - -\begin{figure}[h] -\centerline{\includegraphics[width=5cm]{basicLookup3}} \caption{An instance of the class \ct{EllipseMorph} receives the message \ct{onColor:}. The method lookup starts in the class of the receiver \ct{EllipseMorph}. The method is not defined in this class so it continues in the superclasses and eventually generates an error. \label{fig:basicLookup3}} \end{figure} - -\largecadre{When an object receives a message the lookup for a method with the same selector start \emph{in the class of the receiver}. } - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{Hiding Superclass Method} -As the method lookup starts in the class of the receiver, it is possible to define a new behavior for the inherited definition of a given method in a subclass. We just have to define a method with the same name that the one of the method in the subclass. Such a practice is called \emph{method hidding}\index{method hidding}, \emph{method redefinition}\index{method redefinition} or \index{method overridding}\emph{method overridding}. This practice is commonly used to let a subclass defines its own specific behavior. For example the class \ct{Morph} defines the method \ct{stepTime} that returns the period of time at which the \ct{step} method is called as shown in~\mthref{mth:MorphstepTime}, while the class \ct{Flasher} redefines this method as shown in~\mthref{mth:MorphstepTime}. - -Figure~\ref{fig:basicLookup4} shows how the two different \ct{stepTime} methods defined on different classes are looked up. As we already repeated multiple times, finding the right method is based on the fact that the lookup starts in the class of the receiver then continues in the class superclasses. - -\begin{figure} -\centerline{\includegraphics[width=7cm]{basicLookup4}} \caption{Method hidding or redefinition. An instance of the class \ct{Flasher} executes a different method \ct{stepTime} than an instance of the class \ct{Morph}.\label{fig:basicLookup4}} \end{figure} - -\begin{method}\label{mth:FlasherHerestepTime} -Flasher>>stepTime "Answer the desired time between steps, in milliseconds." ^ 500 -\end{method} - -\begin{method}\label{mth:MorphstepTime} -Morph>>stepTime "Answer the desired time between steps in milliseconds. This - default implementation requests that the 'step' method be - called once every second." ^ self topRendererOrSelf player ifNotNil: [10] ifNil: [1000] -\end{method} - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{The Dynamic Aspect of Self Sends} - -The variable \ct{self} represents the receiver of the message. This has a subtle but really powerful consequence. Even if \ct{self} is used in a method found in a superclass of the receiver class, the variable \ct{self} represents the \emph{original receiver}. This implies that all the messages sent to \ct{self} will be looked up starting from \emph{its class}. Therefore methods defined on the class of the receiver take precedence as they may override superclass methods. - -Figure~\ref{fig:dynamicSelf1} illustrates this important point. An instance of the class \ct{Flasher} receives the message \ct{startSteppingIn: World}. The method \ct{startSteppingIn:} is found on the class \ct{Morph}. When the message \ct{self step} contained in the method startSteppingIn: is executed, the message \ct{step} is sent to the receiver of the original message: the instance of the class \ct{Flasher}. Therefore this is the method \ct{step} defined on the class \ct{Flasher} and not \ct{Morph} that is executed. - -It is common to say that \ct{self} is \emph{dynamic} by reference to the fact that the lookup \emph{always} starts in the class of the receiver. - -\begin{figure} -\centerline{\includegraphics[width=10cm]{dynamicSelf1}} \caption{Self-sends are always looked up starting in the class of the receiver. For the message \ct{self step}, the method \ct{step} is looked up starting in the class \ct{Flasher}, even if the method \ct{startSteppingIn:} which sends this message is defined on the class \ct{Morph}.\label{fig:dynamicSelf1}} \end{figure} - -\largecadre{Messages sent to the variable \ct{self} are \emph{always} looked up by starting from the class of the \emph{receiver} and continuing in its superclasses, even if there are defined in superclass methods.} - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{Extending Ancestor Behavior} -Up until we saw how we can add new behavior to a subclass or redefine a behavior by hidding the existing behavior of a superclass. There are occasions where we would like to be able to \emph{extend} the behavior of the superclass, \ie\ to execute it but also to add some specific actions. This is what we needed for the flasher morph. For example, we needed to specialize the methods \ct{step} so that in addition to changing the color of the morph to make it flashing it also moves it. - -\st and other object-oriented languages provide a way to invoke hidden methods. We send messages to the variable \ct{super}. In \mthref{mth:stepinh} defined on the class \esf we have to invoke the \ct{step} methods defined higher in the superclasses so we send the message \ct{step} to the variable \ct{super}. Note methods that are not hidden can just be called by sending a message to \ct{self}. - -\begin{method}\label{mth:stepinh} -Flasher>>\bold{step} "Perform my standard periodic action" \bold{super step.} self color = self onColor ifTrue: [self color: (onColor alphaMixed: 0.5 with: Color black)] ifFalse: [self color: onColor] -\end{method} - -Note that the place where you use \ct{super} does not have to be the first expression of the method body. Most of the time it is because we first want to execute the hidden methods and then later performing extra actions. But you may use \ct{super} everywhere you use \ct{self}. The variable \ct{super} is like the variable \ct{self} it represents the receiver of the message. However, the variable \ct{super} changes the way methods are lookep up as we show below. -Normally you only need to use \ct{super} when you are defining a method in a class and at the same time we have to invoke the method you are hiding by defining this method in your class. If you are not hidding a method just use \ct{self}. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{Understanding \ct{super}} -Understanding exactly how \ct{super} works may be confusing at first. We explain you how it works by showing you how sending a message to \ct{super} changes the way the methods are looked up. \ct{super} like \ct{self} is a variable that represents the \emph{receiver of the message}. However \ct{super} changes the way the methods are looked up. Sending a message to \ct{super} starts the method lookup in the superclass of the class of the method \emph{containing} that message. - -\paragraph{Illustration.} -Figure~\ref{fig:superStatic1} presents two scenarios. The first scenario (noted with a big 1) is the following one: the message \ct{step} is sent to an instance of the class \ct{Flasher}, the method lookup starts in this class. The method \ct{step} defined in the class \ct{Flasher} is found and executed. This method sends a message \ct{step} to the variable \ct{super}, so the lookup does not start in the class of the receiver, \ct{Flasher} but in the superclass of the class that contains the first method \ct{step}, the class \ct{EllipseMorph}. - -The second scenario (noted with a big 2) is here to stress the key point in the change of the lookup. The message \ct{step} is sent to an instance of the class \ct{MovingFlasher} subclass of the class \ct{Flasher}, the method lookup starts then in this class. The method \ct{step} defined in the class \ct{Flasher} is found and executed. This method sends a message \ct{step} to the variable \ct{super}, \emph{so the lookup does not start in the class of the receiver, \ct{MovingFlasher} nor in \ct{Flasher} but in the superclass of the class that contains the first method \ct{step}, the superclass of \ct{Flasher}: the class ct{EllipseMorph}}. - -This second scenario illustrates that contrary to \ct{self} send messages for which the method lookup starts \emph{always} from the class of the receiver, \ct{super} is not influenced by the receiver. Looking up method for super sends starts always in the superclass of the class that contain them. - -\largecadre{Sending a message to \ct{super} starts the method lookup in the superclass of the class of the method \emph{containing} that message.} - - -\begin{figure} -\centerline{\includegraphics[width=12cm]{superStatic1}} \caption{\ct{super} is dynamic because it always represents the receiver of the message. This means that all the message sends sent to \ct{self} are looked up by starting in the class of the receiver. \label{fig:superStatic1}} \end{figure} - - -\begin{spicy} -It is common that people think that \ct{super} starts by looking up the method in the superclass of the class of the receiver. It happens that professionals, experts or even book authors think that \ct{super} sends work this way. If the semantics of \ct{super} sends would be this one no object-oriented language would work because it would loops. Follow the lookup of the method \ct{step} in the second scenario of Figure~\ref{fig:superStatic1}. You should see that the superclass of the class of the receiver is \ct{Flasher} so this means that \ct{Flasher>>step} would called itself and would lead to an infinite loop. -\end{spicy} - -\largecadre{The method lookup for a message sent to \ct{super} starts in the superclass of the class of the method \emph{containing} that message.} - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{Defining a New Class} -Now it is time to create a first class. Imagine that we want to implement a \emph{moving} flasher. Instead of defining all the behavior of this new class from scratch, we want to reuse the behavior of the class \ct{Flasher}. For this purpose we define a new class \ct{MovingFlasher} as subclass of the class \ct{Flasher} or said in a different but equivalent way we create the class \ct{MovingFlasher} that inherits from the class \ct{Flasher}. - -Per default \sq presents the following template for class definition as a class always inherits from another one and per default inherits from the class \ct{Object} root of inheritance tree. - -\begin{classdef}\label{movingflasher} -Object subclass: #NameOfSubclass instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo' -\end{classdef} - -We do not want to inherit from the class \ct{Object} but from the class \ct{Flasher}. Therefore we define the class \ct{MovingFlasher} as shown by the class definition~\ref{movingflasherClass}. We do not need extra state so we do not specify new any instance variables. - -\begin{classdef}\label{movingflasherClass} -Flasher subclass: #EscapingFlasher instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo' -\end{classdef} - -Now we would like that the new flashers get located in the center of the screen. For this purpose we \emph{extend} the method \ct{initializeToStandAlone} as shown by \mthref{mth:MovingFlasherinitializeToStandAlone}. The method \ct{initializeToStandAlone} is automatically called by the method \ct{newToStandAlone} which creates new instances. It is the place to define behavior that have to be executed when a new object is created. - -\begin{method}\cat{initialize}\label{mth:MovingFlasherinitializeToStandAlone} -MovingFlasher>>initializeToStandAlone "MovingFlasher newStandAlone" super initializeToStandAlone. self position: World center -\end{method} - -Note that calling the hidden method \ct{initializeToStandAlone} is really important, as it initializes correctly some aspects of the morphic system. If you forget this message the newly created morph will not work correctly because only the method \ct{initializeToStandAlone} of the class \ct{MovingFlasher} will get invoked as it hides superclass methods. This shows that reuse comes at the price to know what you are doing and knowing a bit the logic of the superclasses. - -Now if you execute the expression \ct{MovingFlasher newStandAlone openInWorld}, a flasher appears in the center of the \sq environment. Still it is not moving because we did not define that behavior yet. -Define the method \ct{direction} which returns randomly a point representing the amount of pixels of which the flasher should move. - -\begin{method} -MovingFlasher>>direction | x | x := 20 atRandom. ^ x - 10 @ (x - 10) -\end{method} - -Then define the method \ct{step} as shown in method definition~\ref{mth:MovingFlasherstep}. This method checks whether the moving flasher is visible or not. When it is visible it just changes its location by adding the point returned by the method \ct{direction} to its current position, else it resets the morph in the center. - -\begin{method}\cat{stepping}\label{mth:MovingFlasherstep} -MovingFlasher>>step "MovingFlasher newStandAlone openInWorld" \bold{super step}. (World bounds containsPoint: self position) ifTrue: [self position: self position + self direction] ifFalse: [self position: World center] -\end{method} - -We suggest you to comment the \ct{super step} expression in the -\ct{MovingFlasher>>step} to see that when you comment it, the morph will move but not flash anymore as the behavior of the class \ct{Flasher} will not be called. - -If you want a different moving behavior, you just have to define a new subclass of \ct{MovingFlasher} and define a new \ct{direction} method. Here the morph will move in diagonal in the direction of the right bottom corner, hence we named the class \ct{DiagonalMovingMorph}. - -\begin{method} -DiagonalMovingMorph>>direction ^ 10 @ 10 -\end{method} - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{About Class Methods} -The final point that we want to explain is the class methods. -In \st a class is an object therefore we can define class methods \ie -methods that are executed on object that are classes. -What is key to understand is that everything we did for an instance is \emph{exactly} the same for a class. When we send a message to caro, the method is lookup in the class of caro \ie\ Turtle. When we send the message \ct{new} to the class \ct{Turtle}, the method is looked up in the class of the class \ct{Turtle}. The class of the class \ct{Turtle} is called \ct{Turtle class}. - -\begin{figure} -\centerline{\includegraphics[width=12cm]{lookupEssenceClass}} \caption{When an object receives a message the method is looked up in the class of the object. When a message is sent to a class it is looked up in the class of the class.\label{fig:lookupEssenceClass}} \end{figure} - -To edit or browse a class method in \sq you have to press on the class button in the System Browser as shown in Figure~\ref{fig:classButton}. -Then when you define a method, this method will be executed when you -send a message to a class. For example the class method~\ref{mth:penclassexample} \ct{example} of the class \ct{Pen} is defined as follow. Then you can invoke this method \ct{Pen example}. - - -\begin{method}\label{mth:penclassexample} -Pen class>>example "Draw a spiral with a pen that is 2 pixels wide." "Display restoreAfter: [Pen example]" | bic | bic := self new. bic defaultNib: 2. bic color: Color blue. bic combinationRule: Form over. 1 to: 100 do: [:i | bic go: i*4. bic turn: 89]. -\end{method} - - - -\begin{figure} -\centerline{\includegraphics{classButton}} \caption{The browser helps to access to the class of a class by clicking on the class button. \label{fig:classButton}} \end{figure} - - - - -\summa - -\begin{itemize} -\item A class inherits behavior and state from superclasses. It just have to define new and different behavior. - -\item A class can add behavior, state in addition to the ones it inherits. It can also redefine or extend inherited behavior. -\item The method lookup for a message sent to \ct{super} starts in the superclass of the class of the method \emph{containing} that message. - -\item A class can add behavior, state in addition to the ones it inherits. It can also redefine or extend inherited behavior. - -\item When an object receives a message the lookup for a method with the same selector start \emph{in the class of the receiver}. - -\item Messages sent to the variable \ct{self} are \emph{always} looked up by starting from the class of the \emph{receiver} and continuing in its superclasses, even if there are defined in superclass methods. - -\item Sending a message to \ct{super} starts the method lookup in the superclass of the class of the method \emph{containing} that message. -\end{itemize} - -\ifx\wholebook\relax\else -\end{document}\fi diff --git a/Chapters/Introduction/Introduction.md b/Chapters/Introduction/Introduction.md new file mode 100644 index 0000000..d3f889e --- /dev/null +++ b/Chapters/Introduction/Introduction.md @@ -0,0 +1,2 @@ +## About this book @ch:about ### A word of presentation I started to write this book back in 1998 when I wrote around 900 pages in preparation for _Learning Programming with Robots_ \(Apparently, I needed to write to understand what I wanted to explain and how\). From this I extracted _Learning Programming with Robots_, which was a book to teach simple concepts such as variables, loops, procedures and to help people teach kids how to program. My original objective was to write a second volume to teach object-oriented programming. But while this first volume was a success, I got really frustrated because to be understandable by everyone I had to remove what I like: object-oriented programming and good object-oriented design. At that time, I met Harald Wertz, who gave me really nice ideas and pointers such as L-systems, then asked why I focused on procedural thinking and suggested that I should teach object-oriented programming instead. And he was right. This remark was like a bee in my bonnet for more than ten years. In fact, it was my original objective but I was exhausted after my first attempt and I had to focus on my academic life. Now, nearly fifteen years later, I'm ready to write a book to start with object-oriented programming. In fact, I rewrote everything I got from that time. I hope that you will enjoy it as much as I did — even if, for me, writing a book is a really long and daunting task because I want to make it great. I plan to write another volume on patterns of design that will extend this book. ### Structure of the book While writing this book, I faced a challenge to find the correct level of difficulty. To solve this problem, I structured the book either into key chapters on basic concepts, or into projects on more advanced topics. The projects are little tutorials or more realistic examples, with step by step explanations; you can skip over them and come back to read them whenever you feel like it. I also propose various paths through the book with different levels of reading; however, many of the _simpler_ chapters also contain design remarks. ![Reading maps.](figures/MapVolume1.pdf width=100&label=fgmap) #### Fast track The following chapters contain more conceptual information: In the volume 1: - Glimpse of the syntax - Tests, tests and tests - Objects and classes - Revisiting objects and classes - Domain specific language - Inheritance and expressions - Sending messages - Snakes and ladders The other chapters are more exercise and practical. For example, with TinyChat, you will have fun with a web server written in the single page of code. You will find the solutions of the exercises in a separate pdf available on the book web site at [http://books.pharo.org](http://books.pharo.org) and the associated github repository [https://github.com/SquareBracketAssociates/LearningOOPWithPharo](https://github.com/SquareBracketAssociates/LearningOOPWithPharo). ### What you will learn I would like to present the concepts that I want to teach you and that hopefully you should acquire. What is key to understand is that I will focus on the _key_ conceptual elements. It is easy for me because I will not explain OOP/D in general but within the context of Pharo and Pharo is the essence of Object-Oriented programming since its object model is minimal but it covers the key and essential aspect of OOP. For example, we will not present method modifiers, types, or overloading \(which is a bad concept\). We will focus on object-oriented _programming_ concepts: - Objects / Classes - Messages / Methods - `self` and its semantics - Inheritance - `super` and its semantics …and on object-oriented _design_ concepts: - Class responsibility collaboration - Delegation - Message sends are choice - Message sends are plans for reuse - The "Don't ask, tell" Principle - Tests are your life insurance - Polymorphism In addition, we will also present - Tests - Software refactorings ### Pharo version The current book was written with Pharo 8.0 as support. Only some exercises such as TinyChat depends on Pharo 8.0. We suggest to use it as much as possible. #### Growing software Often books present a problem and its solution. Now for non-trivial problems, the solution does not fall from the sky or get developed in one stroke, but it is the constant evolution of a first solution that evolves over time. Such an evolution is often difficult and tedious because the developer jumps from one stable state to a situation where his code may not work anymore. This is where Test Driven Design and refactorings really help. Test Driven Design helps focusing on new features and captures them as executable entities: tests. Refactorings help by transforming code without breaking its invariants. Note that tests do not forbid to break code, they help identifying when previous invariants or constraints got violated. Sometimes violated tests identify a bug, but they may be broken just because the requirements changed and that the tests should be updated. In this book, I wanted to see how software grows in little steps. This is what I do frequently during my coding sessions, and I think that this is important, to cover the hidden paths in software creation. #### Syntax, blocks and iterators Since we need a language to express our programs, we will teach you the syntax of Pharo. In particular, we will use some simple chapters to get you started. Now in a nutshell, you should know that the Pharo syntax - fits in one postcard and - is based on objects, messages and closures. Note that closures are not a recent addition to the language but a central cornerstone. Closures are the foundation for conditionals and loops. They enable this 'messages all over the place' syntax as well as really powerful iterators. ### Typographic conventions Pharo expressions or code snippets are represented either in the text as `'Hello'` and `'Hello' reversed`, or for more substantial snippets, as follows: ``` 'Hello' ``` When we want to show the result of evaluating an expression, we show the result after three chevrons `>>>` on the next line, like so: ``` 'Hello' reversed +>>> 'olleH' ``` Whenever we feel the text makes a point that is important or technical enough to be highlighted, we will do so with a thick bar: !!important This is a point that is worth drawing some more attention. Finally, the coffee cups highlight some points to take away and serve as a concise summary of the sections: !!coffee If you skim through a section, take a few seconds to check for coffee cups! ### Videos While reading this book, you can also use some of the videos produced for the Pharo mooc. All the videos are available at [http://mooc.pharo.org](http://mooc.pharo.org). I strongly suggest to watch the videos explaining how to use and interact with the environment. ### Thanks I would like to thank Morgane Pigny, Anne Etien, Quentin Ducasse, Sven van Caekenberghe, Hayatou Oumarou, Kateryna Aloshkina, Ricardo Pacheco, Olivier Auverlot, Mariette Biernacki, Herby Vojcik, Denis Kudriashov, Holger Freyther, Dimitris Chloupis, Amal Noussi, René Paul Mages, Hannes Hirsel, Lorenzo Solano Martinez, Ludvik Galois for their great feedback. Alexandre Bergel for his examples on messages. Olivier Auverlot for his constant enthousiam and for TinyChat. Guillermo Polito for the idea of file and directory example. Damien Pollet for this great template and the new LAN implementation and the numerous makefile implementation and Pillar help. \ No newline at end of file diff --git a/Chapters/JoeTheBox/JoeTheBox.pillar b/Chapters/JoeTheBox/JoeTheBox.txt similarity index 98% rename from Chapters/JoeTheBox/JoeTheBox.pillar rename to Chapters/JoeTheBox/JoeTheBox.txt index 22d90fd..9fba60f 100644 --- a/Chapters/JoeTheBox/JoeTheBox.pillar +++ b/Chapters/JoeTheBox/JoeTheBox.txt @@ -247,13 +247,13 @@ the methods as if they were defined in the method body. For example, we are assigning ==50== in the instance variable ==size==. The instance variable ==size== is accessible from any method of the class ==Box==. -Contrary to the \index{local variables} of a +Contrary to the local variables of a script (==| caro |== for example) which do not exist after the script execution, instance variables last the complete object life-time. We propose you some experiments to really understand this phenomena below. Note that this behavior is not new, we used it constantly with the turtle. For example, we changed the direction of the turtle using the method -\ct{north} which was somehow changing the internal turtle state representing its direction, then later used the direction to perform some other actions. +==north== which was somehow changing the internal turtle state representing its direction, then later used the direction to perform some other actions. We propose you to do some experiments to really understand this notion. Define the methods \ct{size} (\mthref{mt:size}) which returns the value @@ -552,17 +552,3 @@ BoxT>>drawWithColor: aColor turtle turnLeft: 90] \end{method} - - - -\section{What you should have learned} -Blabla here... - - - - - - - -\ifx\wholebook\relax\else -\end{document}\fi diff --git a/Chapters/Katas/GramKatas.md b/Chapters/Katas/GramKatas.md new file mode 100644 index 0000000..ce322c3 --- /dev/null +++ b/Chapters/Katas/GramKatas.md @@ -0,0 +1,143 @@ +## Some collection katas with words @cha:katas This chapter proposes some little challenges around words and sentences as a way to explore Pharo collections. ### Isogram An isogram is a word or phrase without a repeating letter. The following words are examples of isograms in english and french: - egoism, sea, lumberjacks, background, hacking, pathfinder, pharo - antipode, altruisme, absolument, bigornaux Isograms are interesting words also because they are often the basis of simple ciphers. For instance, isograms of length 10 are commonly used by salespeople to encode numbers and write down the original cost of their products. Using the _pathfinder_ cipher we can decide that _p_ represents the number 1, _a_ represents the number 2 and so on. The price tag for an item selling for 1100 Euros may also bear the cryptic letters _frr_ written on the back or bottom of the tag. A salesman familiar with the pathfinder cipher will know that the original cost of the item is 500 Euros and he can control his sale. Since we will essentially manipulate strings, let us start with some basic knowledge on strings. ### About strings A string in Pharo is in fact an array of characters. We can access string elements using the message `at: anIndex`. Since all collections in Pharo have their first elements at index 1, the message `at: 1` returns the first element of a string. ``` 'coucou' at: 1 +>>> $c ``` ``` 'coucou' at: 3 +>>> $u ``` As with any collection, we can apply iterators such `select:`, `do:`, or `collect:`. Here we select all the characters that are after, hence bigger, than character `$m`. ``` 'coucou' select: [ :aChar | aChar > $m ] +>>>'ouou' ``` We can also apply all kinds of operations on the collection. Here we reverse it. ``` 'coucou' reverse +>>> 'uocuoc' ``` We can also find the index of a string inside another one using the message `findString: aString startingAt: anIndex`. ``` 'coucou' findString: 'ou' startingAt: 1 +>>> 2 ``` ``` 'coucou' findString: 'ou' startingAt: 3 +>>> 5 ``` We simply present some of the possible messages that can be sent to a string. We select some that you can use in the following or in the next chapter. Now let us solve our problem to identify if a string is an isogram. ### A solution using sets We can do a simple \(and not really efficient\) implementation using sets. Sets are collections that only contain one occurence of each element added to them. Adding twice the same element only adds one. Note that sets in Pharo can contain any objects, even sets themselves. This is not the case in all languages. In Pharo, there is no restriction about set elements. You can convert a string into a set of characters sending the string the message `asSet`. ``` 'coucou' asSet +>>> a Set($u $c $o) ``` Now this is your turn: Imagine how using a set of characters, you can determine if a string is a isogram. #### Hints If the size of a set with the contents of a string and this string are the same, it means that the string does not contain any letter twice! Bingo we can simply identify an isogram. To get the size of a collection use the message `size` ``` 'coucou' size +>>> 6 ``` Now we convert `'coucou'` into a set using the message `asSet`. ``` 'coucou' asSet size +>>> 3 ``` Note that the message `asSet` is equivalent to the following script: ``` | s1 | +s1 := Set new. +'coucou' do: [ :aChar | s1 add: aChar ]. +s1 +>>> a Set($u $c $o) ``` - Here we define a variable `s1` - We iterate over all the characters of the string `'coucou'`, and we add each character one by one to the set `s1`. - We return the set. - The set contains only three elements `$c`, `$o`, `$u` as expected. #### Checking expression So now we can get to the expression that verifies that `'pharo'` is an isogram. ``` | s | +s := 'pharo'. +s size = s asSet size +>>> true ``` And that `'phaoro'` is not! ``` | s | +s := 'phaoro'. +s size = s asSet size +>>> false ``` #### Adding a method to the class String Now we can define a new method to the class `String`. Since you may propose multiple implementations, we postfix the message with the implementation strategy we use. Here we define `isIsogramSet` ``` String >> isIsogramSet + "Returns true if the receiver is an isogram, i.e., a word using no repetitive character." + "'pharo' isIsogramSet + >>> true" + "'phaoro' isIsogramSet + >>> false" + + ... Your solution here ... ``` And we test that our method is correct. ``` 'pharo' isIsogramSet +>>> true ``` ``` 'phaoro' isIsogramSet +>>> false ``` Wait! We do not want to have to check manually all the time! !!coffee When you verify two times the same things, better write a test! Remember you write a test once and execute it million times! ### Defining a test To define tests we could extend the `StringTest` class, but we prefer to keep our experiment contained in a package of its own. To achieve this, we will create a package `LoopStarGram` and define the `String >> isIsogramSet` method as a class extension of that package. !!important To define a method as a class extension of package `Foo`, just name the protocol of the method `*Foo`. We define the class `GramCheckerTest` as follow. It inherits from `TestCase` and belongs to the package `LoopStarGram`. ``` TestCase subclass: #GramCheckerTest + instanceVariableNames: '' + classVariableNames: '' + package: 'LoopStarGram' ``` Now we are ready to implement our first automated test for this chapter. Test methods are special. - A test method should start with `'test'`. - A test method is executed automatically when we press the icons displaying the method. - A test method can contain expressions such as `self assert: aTrueExpression` or `self deny: aFalseExpression`. Here - Our method is named `testIsogramSetImplementation`. - We check \(`assert:`\) that 'pharo' is an isogram i.e., that `'pharo' isIsogramSet` returns `true`. - We check \(`deny:`\) that 'phaoro' is _not_ an isogram i.e., that `'phaoro' isIsogramSet` returns `false`. ``` GramCheckerTest >> testIsogramSetImplementation + self assert: 'pharo' isIsogramSet. + self deny: 'phaoro' isIsogramSet. ``` !!important When you write a test, make sure that you test different situations or results. Why? Because imagine that your methods always return true, you would never be sure that not all the string are isograms. So always check for positive and negative results. Messages `assert:` and `deny:` are equivalent as follows: assert \(something\) is equals to deny\(something not\) and assert \(something not\) is equivalent to deny \(something\). Hence the following expressions are strictly equivalent. ``` self assert: 'phaoro' isIsogramSet not. + self deny: 'phaoro' isIsogramSet. ``` #### Testing several strings Writing a test for each new string is tedious; we want to exercise the same test on multiple strings. For that we will define a method in the test class that returns a collection of strings, for instance an array of isograms. ``` GramCheckerTest >> isograms + ^ #('pharo' 'pathfinder' 'xavier' 'francois' 'lumberjacks' 'altruisme' 'antipode') ``` Then we define a new test method `testAllIsogramSet` that simply iterates over the string array and for each verifies using `assert:` that the element is indeed an isogram. In Pharo, there are multiple ways to express loops on collections, the easiest being to send the message `do:` to the collection. The `do:` message executes the block on each element of the collection one by one. !!coffee The `do:` message executes its argument taking each element of the receiver collection one by one. Note the way we express it, we ask the collection to iterate on itself. Note also that we do not have to worry about the size of the collection and the index of an element as this is often the case in other languages. ``` GramCheckerTest >> testAllIsogramSet + + self isograms do: [ :word | + self assert: word isIsogramSet ] ``` Since we said that we should also tests negative let us to the same for non isograms. We create another method that returns non isogram strings and we enhance our testing method. ``` GramCheckerTest >> notIsograms + ^ #('phaoro' 'stephane' 'idiot' 'freedom') ``` And we make our test using both. ``` GramCheckerTest >> testAllIsogramSetImplementation + + self isograms do: [ :word | + self assert: word isIsogramSet ]. + self notIsograms do: [ :word | + self deny: word isIsogramSet ] ``` ### Some fun: Obtaining french isograms Now we would like to find some isograms in french. We stored on the github repository of this book some resources as shown below containing french words line by line. We would like to get all the lines. We will use `ZnClient`, the HTTPClient that comes with Pharo. Since this is a lot of data, execute the expression using the **Inspect It** menu or shortcut to get an inspector instead of a simple **Do It**. You can try the other file which contains more than 330 000 french words. ``` (ZnClient new + get: 'https://raw.githubusercontent.com/SquareBracketAssociates/ + LearningOOPWithPharo/ + master/resources/listeDeMotsAFrancaisUTF8.txt') lines + +(ZnClient new + get: 'https://raw.githubusercontent.com/SquareBracketAssociates/ + LearningOOPWithPharo/ + master/resources/listeDeMotsFrancaisFrGutUTF8.txt') lines ``` The expression above will give you an array of 336531 words \(it is a bit slow depending on your internet connection because it is lot of data\). % @@add screenshot Once you get the inspector opened, you can start to play with the data. Make sure that you select `self` and in the text pane you can execute the following expressions: The first one will select all the words that are isogram and you will see them in the second list that will appear on the right. ``` self select: #isIsogramSet ``` Now you can select again all the isogram longer or equal to 10. ``` self select: [:each | each size >= 10 ] ``` We have other ways to implement isograms and we will discuss such implementations in the next chapter. Now we will play with pangrams. ### Pangrams The definition of a pangram is the following: _A Pangram or holoalphabetic sentence for a given alphabet is a sentence using every letter of the alphabet at least once_. Here are examples of english pangrams: - the five boxing wizards jump quickly - the quick brown fox jumps over the lazy dog Let us write a test first. Yes we want to make sure that we will be able to control if our code is correct and we do not want to repeat typing the test. ``` GramCheckerTest >> testIsEnglishPangram + + self assert: 'The quick brown fox jumps over the lazy dog' isEnglishPangram. + self deny: 'The quick brown fox jumps over the dog' isEnglishPangram ``` #### Imagine a solution Imagine that we have a collection or string representing the alphabet. A solution is to check that the potential pangram string contains each of the characters of the alphabet, as soon as we see that one character is missing we stop and know that the sentence is not a pangram. ``` 'The quick brown fox jumps over the lazy dog' isEnglishPangram +>>> true +'The quick brown fox jumps over the dog' isEnglishPangram +>>> false ``` #### A first version Here is a first version. We define a variable `isPangram` that will represent the information we know about the receiver. We set it to true to start. Then we iterate over an alphabet character by character and as soon as the character is not included in the receiver we set the variable to false. At the end we return the variable `isPangram`. ``` String >> isEnglishPangram + "Returns true is the receiver is a pangram i.e., that it uses all the characters of a given alphabet." + + | isPangram | + isPangram := true. + 'abcdefghijklmnopqrstuvwxyz' do: [ :aChar | + (self includes: aChar) + ifFalse: [ isPangram := false ] + ]. + ^ isPangram ``` This first implementation has a problem. Can you see which one? If the sentence does not contain `$a`, we will know it immediately still we will look for all the other letters. So this is clearly inefficient. #### A better version Instead for testing all characters, even if we know one is missing, what we should do is to stop looking as soon as we identify that there is one missing character and return the result. The following definition is doing this and it deserves a word of explanation. The expression `^ something` returns the value `something` to the caller method. The program execution exits at that point: it does not execute the rest of method. The program execution returns to the method caller. Usually we use `^ something` as last statement of a method when they need to return a special value. Now `^ anExpression` can occur anywhere and in particular inside a loop. In such a case the loop is stopped, the method execution is stopped and the value is returned. ``` String >> isEnglishPangram + "Returns true is the receiver is a pangram i.e., that it uses all the characters of a given alphabet." + + 'abcdefghijklmnopqrstuvwxyz' do: [ :aChar | + (self includes: aChar) + ifFalse: [ ^ false ] + ]. + ^ true ``` Note that we do not need the variable `isPangram` anymore. We return `true` as last expression because we assume that if the execution arrives to this point, it means that all the characters of the alphabet are in the receiver; otherwise the execution would have been stopped and false would have been returned. !!coffee When you define a method returning a boolean value, always think that you should at least return a true and a false value. This sounds like a stupid advice but developing such a basic reflex is important. !!important The execution of any expression preceded by a `^` \(a caret\) will cause the method to exit at that point, returning the value of that expression. A method that terminates without explicitly returning some expression will implicitly return `self`. ### Handling alphabet A pangram is only valid within a given alphabet. The web site [http://clagnut.com/blog/2380/](http://clagnut.com/blog/2380/) describes pangrams in many different languages. Now we could follow one gag in Gaston Lagaffe with the 'Il y a des poux. Parmi les poux, il y a des poux papas et des poux pas papas. Parmi les poux papas, il y a des poux papas papas et des poux papas non papas....' and all their descendance. 'les poux papas et les poux pas papas' is not a pangram in french but a pangram in the alphabet 'apouxetl'. We would like to be able to specify the alphabet to be used to verify. Yes we define a new test. ``` GramCheckerTest >> testIsPangramIn + + self assert: ('The quick brown fox jumps over the lazy dog' isPangramIn: 'abcdefghijklmnopqrstuvwxyz'). + self assert: ('les poux papas et les poux pas papas' isPangramIn: 'apouxetl'). ``` You can do it really simply: ``` String >> isPangramIn: alphabet + "Returns true is the receiver is a pangram i.e., that it uses all the characters of a given alphabet." + "'The quick brown fox jumps over the lazy dog' isPangramIn: 'abcdefghijklmnopqrstuvwxyz' + >>> true" + "'tata' isPangramIn: 'at' + >>> true" + + ... Your solution ... ``` ``` String >> isEnglishPangram + "Returns true is the receiver is a pangram i.e., that it uses all the characters of a given alphabet." + "'The quick brown fox jumps over the lazy dog' isEnglishPangram + >>> true" + "'The quick brown fox jumps over the dog' isEnglishPangram + >>> false" + + ... Your solution ... ``` Execute all the tests to verify that we did not change anything. If we keep to use french words that do not need accents, we can verify that some french sentences are also pangrams. ``` 'portez ce vieux whisky au juge blond qui fume' isEnglishPangram +>>> true + +'portons dix bons whiskys à l''avocat goujat qui fume au zoo.' isEnglishPangram +>>> true ``` ### Identifying missing letters Building a pangram can be difficult and the question is how we can identify missing letters. Let us define some methods to help us. But first let us write a test. We will start to write a test for the method `detectFirstMissingLetterFor:`. As you see we just remove one unique letter from our previous pangram and we are set. ``` GramCheckerTest >> testDetectFirstMissingLetter + + self assert: ('the quick brown fox jumps over the lzy dog' + detectFirstMissingLetterFor: 'abcdefghijklmnopqrstuvwxyz') equals: $a. + self assert: ('the uick brown fox jumps over the lazy dog' + detectFirstMissingLetterFor: 'abcdefghijklmnopqrstuvwxyz') equals: $q. ``` **Your work:** Propose a definition for the method `detectFirstMissingLetterFor:`. ``` String >> detectFirstMissingLetterFor: alphabet + "Return the first missing letter to make a pangram of the receiver in the context of a given alphabet. + Return '' otherwise" + + ... Your solution ... + ``` In fact this method is close to the method `isPangramIn: alphabet`. It should iterate over the alphabet and check that the char is included in the string. When this is not the case, it should return the character and we can return an empty string when there is no missing letter. #### About the return values of detectFirstMissingLetterFor: Returning objects that are not polymorphic such as a single character or a string \(which is not a character but a sequence of characters\) is really bad design. Why? Because the user of the method will be forced to check if the result is a single character or a collection of characters. !!coffee Avoid as much as possible to return objects that are not polymorphic. Return a collection and an empty collection. Not a collection and nil. Write methods returning the same kind of objects, this way their clients will be able to treat them without asking if they are different. This practice reinforces the **Tell do not ask principle**. We have two choices: either always return a collection as for that we convert the character into a string sending it the message `asString` as follow, or we can return a special character to represent that nothing happens for example Character space. ``` String >> detectFirstMissingLetterFor: alphabet + "Return the first missing letter to make a pangram of the receiver in the context of a given alphabet. + Return '' otherwise" + + alphabet do: [ :aChar | + (self includes: aChar) + ifFalse: [ ^ aChar asString ] + ]. + ^ '' ``` Here we prefer to return a string since the method is returning the first character. In the following one we return a special character. ``` String >> detectFirstMissingLetterFor: alphabet + "Return the first missing letter to make a pangram of the receiver in the context of a given alphabet. + Return '' otherwise" + + alphabet do: [ :aChar | + (self includes: aChar) + ifFalse: [ ^ aChar ] + ]. + ^ Character space ``` Now it is better to return all the missing letters. #### Detecting all the missing letters Let us write a test to cover this new behavior. We removed the character a and g from the pangram and we verify that the method returns an array with the corresponding missing letters. ``` GramCheckerTest >> testDetectAllMissingLetters + + self assert: ('the quick brown fox jumps over the lzy do' + detectAllMissingLettersFor: 'abcdefghijklmnopqrstuvwxyz') equals: #($a $g). + self assert: ('the uick brwn fx jumps ver the lazy dg' + detectAllMissingLettersFor: 'abcdefghijklmnopqrstuvwxyz') equals: #($q $o). ``` **Your work:** Implement the method `detectAllMissingLettersFor:`. ``` String >> detectAllMissingLettersFor: alphabet + + ... Your solution ... ``` One of the problem that you can encounter is that the order of the missing letters can make the tests failed. You can create a Set instead of an Array. Now our test does not work because it verifies that we get an array of characters while we get an ordered collection. So we change it to take into account the returned collection. ``` GramCheckerTest >> testDetectAllMissingLetters + + self assert: ('the quick brown fox jumps over the lzy do' + detectAllMissingLettersFor: 'abcdefghijklmnopqrstuvwxyz') equals: (Set withAll: #($a $g)). + self assert: ('the uick brwn fx jumps ver the lazy dg' + detectAllMissingLettersFor: 'abcdefghijklmnopqrstuvwxyz') equals: #($q $o) asSet. ``` Instead of explicitely creating a Set, we could also use the message `asSet` that converts the receiver into a Set as shown in the second check. ### Palindrome as exercise We let as an exercise the identification if a string is a palindrom. A palindrome is a word or sentence that can be read in both way. 'KAYAK' is a palindrome. ``` GramCheckerTest >> testIsPalindrome + + self assert: 'ete' isPalindrome. + self assert: 'kayak' isPalindrome. + self deny: 'etat' isPalindrome. ``` #### Some possible implementations Here is a list of possible implementation. - You can iterate on strings and check that the first element and the last element are the same. - You can also reverse the receiver \(message `reverse`\) and compare the character one by one. You can use the message `with:do:` which iterate on two collections. ``` 'etat' reverse +>>> 'tate' ``` ``` | res | +res := OrderedCollection new. +#(1 2 3) with: #(10 20 30) do: [ :f :s | res add: f * s ]. +res +>>> an OrderedCollection(10 40 90) ``` You can also add the fact that space do not count. ``` self assert: 'Elu par cette crapule' isPalindrome. ``` ### Conclusion We got some fun around words and sentences. You should know more about strings and collection. In particular, in Pharo a collection can contain any objects. You also saw is that loops to not require to specify the first index and how to increment it. Of course we can do it in Pharo using the message `to:do:` and `to:by:do:`. But only when we need it. So play with some iterators such as `do:` and `select:`. The iterators are really powerful and this is really important to be fluent with them because they will make you save a lot of time. \ No newline at end of file diff --git a/Chapters/Katas/GramKatasSolution.md b/Chapters/Katas/GramKatasSolution.md new file mode 100644 index 0000000..3ef59fe --- /dev/null +++ b/Chapters/Katas/GramKatasSolution.md @@ -0,0 +1,55 @@ +## Some collection katas solutions @cha:katassolution This chapter contains the solution of the exercises presented in Chapter *@cha:katas@* ### Isogram ``` String >> isIsogramSet + "Returns true if the receiver is an isogram, i.e., a word using no repetitive character." + "'pharo' isIsogramSet + >>> true" + "'phaoro' isIsogramSet + >>> false" + + ^ self size = self asSet size ``` ### Pangrams ``` String >> isPangramIn: alphabet + "Returns true is the receiver is a pangram i.e., that it uses all the characters of a given alphabet." + "'The quick brown fox jumps over the lazy dog' isPangramIn: 'abcdefghijklmnopqrstuvwxyz' + >>> true" + "'tata' isPangramIn: 'at' + >>> true" + + alphabet do: [ :aChar | + (self includes: aChar) + ifFalse: [ ^ false ] + ]. + ^ true ``` ``` String >> isEnglishPangram + "Returns true is the receiver is a pangram i.e., that it uses all the characters of a given alphabet." + "'The quick brown fox jumps over the lazy dog' isEnglishPangram + >>> true" + "'The quick brown fox jumps over the dog' isEnglishPangram + >>> false" + + ^ self isPangramIn: 'abcdefghijklmnopqrstuvwxyz' ``` ### Identifying missing letters ``` String >> detectFirstMissingLetterFor: alphabet + "Return the first missing letter to make a pangram of the receiver in the context of a given alphabet. + Return '' otherwise" + + alphabet do: [ :aChar | + (self includes: aChar) + ifFalse: [ ^ aChar ] + ]. + ^ '' ``` #### Detecting all the missing letters We create a Set instead of an Array because Arrays in Pharo have a fixed size that should be known at creation time. We could have created an array of size 26. In addition we do not care about the order of the missing letters. A Set is a collection whose size can change, and in which we can add the element one by one, therefore it fits our requirements. ``` String >> detectAllMissingLettersFor: alphabet + + | missing | + missing := Set new. + alphabet do: [ :aChar | + (self includes: aChar) + ifFalse: [ missing add: aChar ] ]. + ^ missing ``` ### Palindrome ``` String >> isPalindrome2 + "Returns true whether the receiver is an palindrome. + 'anna' isPalindrome2 + >>> true + 'andna' isPalindrome2 + >>> true + 'avdna' isPalindrome2 + >>> false + " + 1 + to: self size//2 + do: [ :i | (self at: i) = (self at: self size + 1 - i) + ifFalse: [ ^false ] + ]. + ^true ``` \ No newline at end of file diff --git a/Chapters/Katas/GramVariation.md b/Chapters/Katas/GramVariation.md new file mode 100644 index 0000000..f088b7b --- /dev/null +++ b/Chapters/Katas/GramVariation.md @@ -0,0 +1,85 @@ +## Variations around Isograms This chapter propose some little challenges around words and sentences as a way to explore Pharo collections. ### A dictionary-based solution !!todo blbl ``` String >> isIsogramDictionaryImplementation + "'ALTRUISME' isIsogramDictionaryImplementation" + | letters | + letters := Dictionary new. + self do: [ :aChar | + letters at: aChar + ifPresent: [^ false] + ifAbsent: [ letters at: aChar put: 1]. + ]. + ^ true ``` ### A Bag-based solution !!todo blbl ``` String >> isIsogramUsingBagImplementation + "'ALTRUISME' isIsogramUsingBagImplementation" + + | bag | + bag := Bag new. + self do: [ :each | + bag add: each. + ]. + ^ bag sortedCounts first key = 1 ``` ### A solution using the String API !!todo blbl ``` String >> isIsogramFatestImplementation + "'ALTRUISME' isIsogramFatestImplementation" + 1 to: self size -1 do: [ :ix | + (self + findString: (self at: ix) asString + startingAt: ix +1 + caseSensitive: false) ~~ 0 ifTrue: [ ^ false ] + ]. + ^ true ``` ### A recursive approach Up until now we only used iterations to go from one letter to the others. In Pharo you can also define the computation in recursive manner. Before looking at a solution, imagine how you would do it. For example we could define a method that iterates over the string by removing the first element of the string and pass it as argument to an auxialliary method whose goal is to check whether the receiver includes or not the element and invoke the first method when it is necessary to continue the iteration. For example, the method `isIsogramRecursiveImplementation` can invoke the method `isIsogramContainCharacter:` on a subtring and the method `isIsogramContainCharacter:` can check whether its receiver contains the element and else it invokes `isIsogramRecursiveImplementation` to continue the exploration. ``` String >> isIsogramRecursiveImplementation + ... + +String >> isIsogramContainCharacter: aCharacter + ... ``` Here is a possible implementation. ``` String >> isIsogramRecursiveImplementation + "'ALTRUISME' isIsogramRecursiveImplementation" + "'ALTRUISMEA' isIsogramRecursiveImplementation" + ^ self isEmpty + ifTrue: [ true ] + ifFalse: [ self allButFirst isIsogramContainCharacter: self first ] ``` ``` String >> isIsogramContainCharacter: aCharacter + + ^ (self includes: aCharacter) + ifTrue: [ false ] + ifFalse: [ self isIsogramRecursiveImplementation ] ``` Such solution is a solution that comes naturally when using functional languages such as Camel or Scheme. In Pharo it would be more adapted to data structures such as linked lists because it is cheap to get a structure representing the elements except the first one \(basically on linked list is just the next element since it is also a linked list.\). In the case of strings or arrays, `allButFirst` has to build a new string or array and this is costly. ### A low-level solution ``` isIsogramBitImplementation + | i | + i := 0. + self asLowercase do: [ :char | + | val | + val := (char asInteger - 96). + (val between: 1 and: 26) ifFalse: [ ^ false ]. + (i bitAt: val ) == 1 ifTrue: [ ^ false ]. + i := (i bitAt: val put: 1). + ]. + ^ true ``` An interesting observation here is that if #asLowercase is moved to each character instead “val := \(char asLowercase asInteger - 96\).” Then it leads to a 4-5x performance loss . ### Are all solutions finding the same? ``` | lines bits dicts bags strings sets | + lines := (ZnDefaultCharacterEncoder + value: ZnCharacterEncoder latin1 + during: [ + ZnClient new + get: 'http://www.pallier.org/ressources/dicofr/liste.de.mots.francais.frgut.txt' ]) lines. + + bits := lines select: #isIsogramBitImplementation. + dicts := lines select: #isIsogramDictionaryImplementation. + bags := lines select: #isIsogramBagImplementation. + strings := lines select: #isIsogramStringImplementation. + sets := lines select: #isIsogramSetImplementation. + recurs := lines select: #isIsogramRecursiveImplementation. + { bits . dicts . bags . strings . sets} collect: #size. "#(23118 47674 47674 47668 47674)" ``` ### Comparing solutions Pharo proposes tools to measure execution speed \(for example the message `timeToRun` and `millisecondsToRun:` shown below\). When an operation is too fast you cannot measure well \(Check the chapter on profiling of Deep Into Pharo\), so you should execute multiple times the same expressions. Here measuring an expression alone does not really help as you can see. ``` ['PHRAO' isIsogramSetImplementation ] timeToRun. +>>> 0 +Time millisecondsToRun: ['PHRAO' isIsogramSetImplementation ] +>>> 0 ``` Pharo libraries also offers the bench method that gives the number of execution possible per second. \[ \[ \[ \[ 'ALTRUISME' isIsogramSetImplementation \] bench '334,371 per second' \[ 'ALTRUISME' isIsogramStringImplementation \] bench '546,823 per second' \[ 'ALTRUISME' isIsogramUsingBagImplementation \] bench '142,432 per second' \[ 'ALTRUISME' isIsogramDictionaryImplementation \] bench '147,276 per second' \[ 'ALTRUISME' isIsogrambitImplementation \] bench '1,137,976 per second' \[ 'ALTRUISME' isIsogramRecursiveImplementation \] bench "'487,492 per second'" \]\]\] #### Some observations and comments @todo explain - Size of code does not mean anything. Smaller code can be slower because they use more. ### Handling french and the lesson behind à, â, é, è, ê, ë, î, ï, ô, ù, û, ü, ÿ, ç, æ et œ ``` 'ALTRUISME' isIsogramSetImplementation + +'ALTRUISME' isIsogramStringImplementation + +'ALTRUISME' isIsogramUsingBagImplementation + +'ALTRUISME' isIsogramDictionaryImplementation + +'ALTRUISME' isIsogramBit ``` ### So Watch out You could find that it is worth to systematically use low-level messages. It is not a good strategy. Why? They are several reasons: - You may spend a lot of time to find a strong optimised solution. - You may spend a lot of time optimizing a part of the system that does not need to be optimized. - Low-levels solutions are often more difficult to read and understand so if you need something slightly different they may break. For example what is we want to compute isogram in language where the letters are not contiguous. !!important A good engineering practice is: Make it work, make it right, make it fast and not the inverse #### Variations ``` isIsogramBit + "['ALTRUISME' isIsogramBit] bench '337,427 per second'" + + | i | + i := 0. + self do: [ :char | + | val | + val := (char asLowercase asInteger - 96). + (val between: 1 and: 26) ifFalse: [ ^ false ]. + (i bitAt: val ) == 1 ifTrue: [ ^ false ]. + i bitAt: val put: 1 + ]. + ^ true ``` ``` ['ALTRUISME' isIsogramBit] bench '337,427 per second' ``` What you should see is that the execution engine may also support adaptatives optimisations, i.e., depending on the program it executes it can change it on the fly to gain speed. You may wonder why it is not always the case. Why the compiler and execution engine \(virtual machines\) do not always optimise at the maximum, because optimisation takes time and space and that there is a tradeoff to reach. So virtual machines optimise aggressively code that is executed a lot because they see that it is worth doing it. So now we will look at other kind of words or sentences. \ No newline at end of file diff --git a/Chapters/MessageSending/MessageSending.md b/Chapters/MessageSending/MessageSending.md new file mode 100644 index 0000000..e9b7ca3 --- /dev/null +++ b/Chapters/MessageSending/MessageSending.md @@ -0,0 +1,46 @@ +## Sending a message is making a choice @cha:messages In this chapter we explore an _essential_ point of object-oriented programming: Sending a message is making a choice! Object-oriented programming books often present _late binding_: the fact that the method to execute will only be determined at runtime based on the receiver. In fact sending a message uses late binding to select the correct method. I like to use the term _sending a message_ because it stresses that simple actions, such as sending a message, are also a powerful feature when used well. This aspect is often not really well put in perspective in teaching materials. Lectures often focus on inheritance but understanding the power of message passing it crucial to build good design. This point is so central for me that this is the first point that I explain when I start lectures on advanced design to people already understanding object-oriented programming. In addition, most of the Design Patterns are based on the fact that sending a message is actually selecting the correct method based on the message receiver. To illustrate how sending a message performs a dynamic choice, I will start taking a simple example available in the core of Pharo: the Booleans. Pharo defines Booleans as two objects: `true` and `false`. They are so fundamental that you cannot change their value. Still their implementation also use late binding in a really elegant way. I will explain how the Boolean negation and the disjunction \(or\) are implemented. Then I will step back and analyse the forces in presence and their importance. ### Negation: the not message Boolean negation has nothing special in Pharo: negating a boolean returned the negated value! For example the snippets below show this conventional behavior and vice versa. Sending the message `not` to the Boolean `true` returns the Boolean `false`. ``` true not +>>> false ``` ``` false not +>>> true ``` Nothing fancy. Of course the message `not` can be sent to Boolean expressions \(i.e. expressions whose execution return Booleans\) as shown below: ``` (2 * 4 > 3) not +>>> false ``` ``` (#(1 2 3) includes: 5) not +>>> true ``` Now while Pharo follows traditional Boolean logic, what is less traditional is the implementation of the way the computation is done to answer the correct value. ### Implementing not Take a couple of minutes and a piece of paper and think about the way you would implement this message. Try really to write the code for real. #### A first hint. A first hint that I can give you is that the solution \(used in Pharo and that we want to study\) does not use explicit conditional such as `ifTrue:` or `ifTrue:ifFalse:`. Take a bit more time to think how you can implement not. What we can tell you is the solution is not based on bit fiddling and logical operation on small integers. The solution we are looking for is simple and elegant. #### A second hint. The second hint is that `true` and `false` are instances of different classes. `true` is \(the unique\) instance of the class `True` while `false` is \(the unique\) instance of the class `False`. Note the uppercase on class names. This situation is depicted in Figure *@figTrueFalseOnly@*. What you should see is that the fact that the solution has two different classes opens the door to have two different `not` implementations as shown by Figure *@figTrueFalseOnlySelectors@*. Indeed, as we mention in early chapters, we can have one message and multiple methods that we will be selected and executed depending on the receiver of the message. Now you should be ready to get the solution. We should have a definition for the `true` defined in the class `True` and one for `false` in the class `False`. ![The two classes `True` and `False` and their respective unique instances `true` and `false`.](figures/BooleanTrueAndFalseSolelyWithInstances.pdf width=55&label=figTrueFalseOnly) ![Two methods for one message.](figures/BooleanTrueAndFalseWithNotMethodSelectors.pdf width=65&label=figTrueFalseOnlySelectors) #### Studying the implementation ![Two methods for one message each one returning the other instance.](figures/BooleanTrueAndFalseWithNotMethods.pdf width=75&label=figTrueFalseSolution) The implementation of negation \(message `not`\) is defined as illustrated in Figure *@figTrueFalseSolution@* and is shown below. The method `not` of the class `True` simply returns the Boolean `false`. ``` True >> not + "Negation--answer false since the receiver is true." + ^ false ``` ``` False >> not + "Negation--answer true since the receiver is false." + ^ true ``` Figure *@figTrueFalseSolutionLookup@* shows that sending a message to one of the two Booleans selects the method in the corresponding class. What is important to see is that when a method is executed the receiver is from the class \(or subclass we will see that later\) that defines the method. We can also say that when we define a method in a given class we know that the receiver is from this class. Obvious, isn't it! But important. The implementation can then use this information as an execution context. This is exactly what the `not` implementation does. The method `not` defined on the class `True` knows that the receiver is true so it just has to return `false`. ![Sending a message selects the method in the class of the receiver.](figures/BooleanTrueAndFalseWithNotMethodsLookup.pdf width=75&label=figTrueFalseSolutionLookup) !!note When we define a method in a given class we know that the receiver is from this class. Obvious but important. The implementation can then use this information. Now we will see if you get it... Let us try with a slightly more complex example. ### Implementing disjunction Disjunction is also a core functionality of any programming language. In Pharo disjunction is expressed via the message `|`. Here are the traditional tables describing disjunction but expressed in Pharo: first starting with `true` as receiver. | **or** | true | false | | | true | true | true | | | false | true | false | | Here are a couple of examples expressed in Pharo. ``` true | true +>>> true ``` ``` true | false +>>> true ``` ``` false | false +>>> false ``` For the record, in fact the message `|` implements an eager disjunction since it asks the value of its argument even when not needed and Pharo also offers lazy disjunction implemented in the message `or:` which only requests the argument value if needed. #### When receiver is true. Propose an implementation of the disjunction for the first case: i.e. when the receiver is the object `true`. | **or** | true | false | | | true | true | true | | What you should have learned from the implementation of `not` is that you have two different methods taking advantage of the fact that they know what is the receiver during their execution. ``` true | true +>>> true ``` ``` true | false +>>> true ``` ``` true | anything +>>> true ``` When you look at the table we see that when the receiver is `true` the result is the same as the receiver \(i.e. `true`\). In Pharo the method `|` on class `True` express this as follows: ``` True >> | aBoolean + "Evaluating Or -- answer true since the receiver is true." + ^ true ``` #### When receiver is false. Similarly let us study the Boolean table relative to false as receiver. | **or** | true | false | | | false | true | false | | Here are some snippets ``` false | true +>>> true ``` ``` false | false +>>> false ``` ``` false | anything +>>> anything ``` We see that when the receiver is `false`, the result of the disjunction is the other argument. In Pharo the method `|` on class `False` is then as follows: ``` False >> | aBoolean + "Evaluating Or -- answer with the argument, aBoolean." + ^ aBoolean ``` ![Disjunction implementation: two methods.](figures/BooleanNoHiearchyAndInstancesWithOrMethods.pdf width=80) ### About ifTrue:ifFalse: implementation Now you should start to get the principle. Let us see how it works to also express conditional messages such as `ifTrue:ifFalse:`. Yes fundamental messages such as conditionals can be expressed using the same mechanism: late binding. What you see with the following snippet is that message `ifTrue:ifFalse:` is expecting two different blocks as argument. ``` 4 factorial > 20 + ifTrue: [ 'bigger' ] + ifFalse: [ 'smaller' ] +>>> 'bigger' ``` Now you should know that to execute a block you should use the message `value` as illustrated: ``` [1 + 3] value +>>> 4 ``` Block can contain any expressions. The execution of the following block will open the Pharo logo. ``` [ (ZnEasy getPng: 'http://pharo.org/web/files/pharo.png') + asMorph openInWindow ] value ``` Let us come back to the case of condition and in particular to the message `ifTrue:ifFalse:`. Based on the receiver we should execute the corresponding block from the `ifTrue:ifFalse:` method. When the expression \(`4 factorial > 20` in the example above\) is true we should execute the `ifTrue:` argument, when it is false we should execute the `ifFalse:` argument. #### Implementation. The implementations is then simple and elegant. In the `True` class, we want to execute the corresponding block, the one passed as `ifTrue:` argument as shown in Figure *@figFT@*. ``` True >> ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock + ^ trueAlternativeBlock value ``` Similarly in the `False` class, we want to execute the corresponding block, the one passed as `ifFalse:` argument. ``` False >> ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock + ^ falseAlternativeBlock value ``` ![Conditional implementation: again two methods and no explicit tests.](figures/BooleanNoHiearchyIFTMethods.pdf width=100&label=figFT) #### Optimisation. What we show above works! But if you modify it, the modification will not be taken into account. This is because in Pharo `ifTrue:ifFalse:` is used so often and its semantics should not change that the compiler in fact does not send a message but convert it in low-level logic for the virtual machine. Now you can invent your own conditional message `siVrai:siFaux:` for a french version for example and you will see that this implementation works. ### What is the point? Some readers may be intrigued and think that this is spurious because they will never have to reimplement Booleans in their life. This is true even if there are different versions of Boolean logic such as the ternary logic that contains also unknown value. We picked the Boolean examples to illustrate an important point: sending a message is making a choice. The runtime system will dynamically select the method depending on the receiver. This is what is called late binding or dynamic dispatch. Only at execution the correct method is selected. Now the Boolean example is the simplest one I could find to illustrate this point. It is also ground breaking in the sense that it touches something as fundamental as Boolean main operations. Now the choices can be made over several dozens of classes. For example in Pillar the document processing system in which this book is written there are around 59 different classes expressing different parts of a document: section, title, bold, paragraph... and the same principle applies there. The system selects the correct methods to render text, LaTeX or HTML using exactly the same principle. Now most of the time you can express the same using conditions \(except for the Boolean example and this is why I asked you to implement Boolean logic since you do not want to have Boolean logic to be based on condition because this is inefficient\) as follows: ``` emitHTML: stream + self == PRList + ifTrue: [ ... ] + self == PRParagraph + ifTrue: [ ... ] + ... ``` The problems with such explicit conditions is the following: - First, they are cumbersome to write. Even using case statements as in other languages, the logic can become complex. Imagine for 59 cases of Pillar. Here is a small part of the document hierarchy. ``` PRObject #(''properties'') + PRDocumentItem #(''counter'') + PRDocumentGroup #(''children'') + PRDocument #() + PRHeader #(''level'') + PRList #() + PROrderedList #() + PRUnorderedList #() + PRParagraph #() + PRReference #(''reference'' ''parameters'') + PRFigure #() + PRSlide #(''title'' ''label'') + PRText #(''text'')' ``` - Second, such definitions are not modular. It means that adding a new case requires to edit the method and recompile it. While with the dynamic dispatch, we can just add a new class as shown in Figure *@figFat@*. Furthermore this class can just take advantage of an existing one and extend it \(as we will explained in Chapter *@cha:inheritance@*\). ![One single class vs. a nice hierarchy.](figures/Design-FatVsDispatch.pdf width=80&label=figFat) You could think that this is a not a problem but imagine that now for a business you want to ship different products or solutions to your clients. With dynamic dispatch you can simply package alternate code in separate packages and load them independently as shown in Figure *@inhNoFatPackage@*. ![One single class vs. a nice hierarchy.](figures/Design-FatVsDispatchWithPackages.pdf width=80&label=inhNoFatPackage) #### Classes represent choices Sending a message is making a choice. Now the following question is which elements represent choices. Because you can have the possibility to choose something, but if there is only one choice you will not go that far and take advantage of the power of late binding. In fact, classes represent choices. In the `Boolean` case you have two choices: one for true, and one for false. There is a really difference for example between the fat class design \(left in Figure *@figFat@*\) and the modular design \(right in Figure *@figFat@*\) because we see all the choices which can be made at runtime in the latter case. When I do code review, I look at how domain variations are represented and if there are enough classes. What is important to realise is that classes are cheap. It is better to write five little classes than a single huge one. Some \(even smart\) people get confused by measuring complexity of a system using number of classes. Having many classes representing good abstractions with a single responsibility is much better than having a single class exhibiting multiple responsibilities. ### Conclusion Sending a message is really powerful since it selects the adequate method to be executed on the receiver. Now this is even more powerful than that: Remember that when we execute a method, one key information we have at hand is that the receiver is an instance from this class \(or one of its subclasses as we will see later\) and we can take advantage of this information to eliminate tests. Indeed an object executes a method that have been designed to be executed on it. So no need to test more. Now you should start to understand why in Pharo we are picky about the vocabulary: we use sending a message and not calling a method as in other language. Because sending a message conveys much better the idea that the correct method will be selected and that we do not know a priori which one will be executed. In future chapters we will show that sending a message is creating in fact a hook so that other methods can be executed in place. \ No newline at end of file diff --git a/Chapters/OOPNutshell/OOPNutshell.md b/Chapters/OOPNutshell/OOPNutshell.md new file mode 100644 index 0000000..cf5a636 --- /dev/null +++ b/Chapters/OOPNutshell/OOPNutshell.md @@ -0,0 +1,155 @@ +## Objects and classes @cha:objectclass Pharo is a pure object-oriented programming language, i.e., everything in the system is an object i.e., an entity created by a class and reacting to messages. This chapter presents key mechanisms that characterize object-oriented programming: _objects_, _classes_, _messages_ and _methods_. We will also present _distribution of responsibilities_ which is one of the heart of object-oriented programming as well as _delegation_ and _composition_. Each of these mechanisms will be used and illustrated again in this book. We start explaining objects, classes, messages and methods with really simple examples. Then in the following chapter we will propose an example that illustrates what we can achieve by using objects of different classes. _Objects_ are created by _classes_ that are object factories: Classes define the structure and behavior of objects \(in terms of methods\) but each object has a specific state and identity that is unique and different from all other objects. A class defines _methods_ that specify how a _message_ is actually implemented. ### Objects: Entities reacting to messages _Instead of a bit-grinding processor ... plundering data structures, we have a universe of well-behaved objects that courteously ask each other to carry out their various desires._ \[Ingall 81\] Object-oriented programming is about creating objects and interacting with objects by sending them _messages_. Objects are entities that communicate via messages and react to messages by executing certain tasks. Moreover objects hide the way they define these tasks: the client of an object send a message to an object and the system find the corresponding method to be executed. Messages specify what should be done and methods how it should be done. #### Turtles as an example Imagine that we have a graphics turtle like a LOGO turtle. We do the following: create a turtle, send it messages to make it move, turn, and trace some drawings. Let us look at this in detail. % @@todo Add a figure once I have the turtle implemented. #### Creating an object First we create a new turtle by sending the message `new` to the class `Turtle`. ``` | t | +t := Turtle new. ``` A class is a cast for objects. All the objects, instances of a class, share the same characteristics and behavior. For example, all the turtle instances have a direction and understand messages to rotate and move. However, each turtle has its own value for its direction. We say that all the instances of a class have the same instance variables but each as private value for them. #### Sending messages The only way to interact with objects is to send them _messages_. In the following snippets we send messages - to create an object , message `new`, - to tell the turtle to turn, message `turn:`, and - to tell the turtle to move, message `go:`. ``` | t | +t := Turtle new. +t turn: 90. +t go: 100. +t turn: 180. +t go: 100. ``` When an object receives a message, it reacts by performing some actions. An object can return a value, change its internal state, or send messages to other objects. Here the turtle will change its direction and it will interact with the display to leave a trail. #### Multiple instances: each with its own state. We can have multiple objects of the same class and each one has a specific state. Here we have two turles each one located to a specific position and pointing into its own direction. ``` | t1 t2 | +t1 := Turtle new. +t1 turn: 90. +t1 go: 100. +t1 turn: 180. +t1 go: 100. +t2 := Turtle new. +t2 go: 100. +t2 turn: 40. +t2 go: 100. ``` ### Messages and Methods Messages specify _what_ the object should do and not how it should do it \(this is the duties of methods\). When we send the message `go:` we just specify what we expect the receiver to do. Sending a message is similar to the abstraction provided by procedures or functions in procedural or functional programming language: it hides implementation details. However sending a message is much more than executing a sequence of instructions: it means that we have to find the method that should be executed in reaction to the message. #### Message: what should be executed The message `square:` is send to a new turtle with 100 as argument. The message expresses what the receiver should do. ``` Turtle new square: 100 ``` #### Method: how we execute it The method definition `square:` below defines step by step what are the actions to be done in response to the message `square:`. It defines that to draw a square the turtle receiving the message `square:` \(represented by `self`\) should perform four times the following sequences of messages: move forward a distance \(message `go:`\), turn 90 degrees \(using the message `turn:`\). ``` square: size + 4 timesRepeat: [ self go: size; turn: 90 ] ``` Note that finding the method corresponding to the message is done at runtime and depends on the object receiving the message. !!coffee A message represents _what_ the object should do, while a method specifies _how_ the behavior is realized. An object can also send messages to other objects. For example, when a turtle draws a line, it sends messages to an object representing the line color and its length. ![An object presents to the other objects an interface composed of a set of messages defining _what_ he can do. This behavior is realized by methods that specify _how_ the behavior is implemented. When a message is sent to an object a method with the message name \(called selector\) is looked up and executed. ](figures/basicMessageMethod.pdf width=50&label=basicMessageMethod) !!coffee An object is an entity that once created receives messages and performs some actions in reaction. When a message is sent to an object, a method with the message name is looked up and executed. ### An object is a protective entity An object is responsible of the way it realizes its reaction to a message. It _offers services_ but _hides_ the way they are implemented \(see Figure *@fig:encapsulationAtWork2@*\). We do not have to know how the method associated with the message selector is implemented. Only the object knows the exact definition of the method. This is when we define the method `square:` that defines how a turtle draws a square of a given size, that we focus on _how_ a turtle draws a square. Figure *@fig:encapsulationAtWork2@* shows the message and the method `square:`. The method `square:` defines how to draw step by step a square, however the object only offers the message square: and does not show it implementation. !!important An object presents to the other objects an _interface_ \(i.e., a set of messages\) defining _what_ the object can do. This behavior is realized by methods that specify _how_ the behavior is implemented. To perform something useful some data are most of the time required. Data are only accessed by the methods. From a turtle _user_ point of view, the only relevant information is that the turtle effectively receiving the message `square:` executes the method that draws a square. So changing the definition of the `square:` method to the one below does not have any consequence on the methods that call it. Figure *@fig:encapsulationAtWork2@* illustrates this point. ``` square: s + "Make the receiver draw a square of size s" + + self go: s; turn: 90; go: s; turn: 90. + self go: s; turn: 90; go: s; turn: 90 ``` ![The message `square:` can be implemented differently. This different implementation does not impact the sender of the message who is not concerned by the internals of the object.](figures/encapsulationAtWork2.pdf width=50&label=fig:encapsulationAtWork2) Hiding the internal representation is not limited to object-oriented programming but it is central to object-oriented programming. !!important An object is responsible of the way it realizes its reaction to a message. It offers services and hides the way they are implemented. ### An object protects its data An object holds some _private data_ that represents its state \(see Figure *@fig:objectOne@*\). Moreover, it controls its state and should not let other objects play directly with them because this could let him into an inconsistent state. For example, you do not want to somebody else plays with the data of your bank account directly and really want to control your transaction. For example, a LOGO turtle can be represented by a position, a direction and a way to indicate if its pen is up or down. But, we cannot directly access these data and change them. For that we have to use the set of messages proposed by a turtle. These methods constitute the _interface_ of an object. We say that the object state is _encapsulated_, this means that not everybody can access it. In fact, object-oriented programming is based on encapsulation, i.e., the fact that per default objects are the only ones that can access their own state. ![A turtle is an object which has an interface, i.e., a set of messages to which it can reply and a private state that only its methods can access.](figures/privateData.pdf width=45&label=fig:objectOne) In Pharo, a client cannot access the state of an object if the object does not define a method to access it. Moreover, clients should not rely on the internal representation of an object because an object is free to change the way it implements its behavior. Exposing the internal state of an object by defining methods providing access to the object data weakens the control that an object has over its own state. % Our turtle (contrarily to the original Logo turtle) does not provide a way to change directly the status of its pen. The messages ==jump== and ==go== are actually changing the status of the turtle's pen and its position. ![Two turtles have the same interface, i.e., set of messages being understood but they have _different_ private state representing their direction, position and pen status.](figures/privateDataTwoInstances.pdf width=70&label=fig:twoInstances) !!important An object holds some _private_ data that represents its _internal_ state. Each object has its own state. Two objects of the same class share the same _interface_ but have their own private state. ### With counters Now that you got the main point of objects, we can see that it applies to everything. In Pharo _everything_ is an object. In fact there is _nothing_ else, only objects. Here is a little program with counters. We create two counters that we store in variables `c1` and `c2` instances of the class `Counter`. Each counter has its own state but exhibits the same behavior as all the counters defined by the class `Counter`: - when responding to the message `count`, it returns its value, - when responding to the message `increment`, it increment one to its current value. ``` | c1 c2 | +c1 := Counter new. +c2 := Counter new. +c1 count. +>>> 0 +c1 increment. +c1 increment. +c1 count. +>>> 2 +c2 count. +>>> 0 +c2 increment. +c2 count. +>>> 1 ``` ### A class: blueprint or factory of objects A class is a mold or cast of objects. A class specifies two important aspects of their instances: - Instance **structure**. All the instances of a class will have the same structure expressed in terms of _instance variables_. Pay attention that the variables are the same for all the instances of a class but not their values. Each instance has specific values for its instance variables. - Instance **behavior**. All the instances share the same behavior even if this one can be different because applied on different values. !!important A class is as a blueprint for its instances. It is a factory of objects. All objects will have the same structure and share a common behavior. Let us illustrate this with the class `Counter`. #### Object structure Let us study the `Counter` class definition. ``` Object subclass: #Counter + instanceVariableNames: 'count' + classVariableNames: '' + package: 'LOOP' ``` The expresion `Object subclass: #Counter` indicates that the class `Counter` is a subclass of the class `Object`. It means that counter instances understand the messages defined also by the class `Object`. In Pharo, classes should at least be a subclass of the class `Object`. You will learn more about subclassing and inheritance in Chapter *@cha:inheritance@*. Then the class `Counter` defines that all the instances will have one instance variable named `count` using the expression `instanceVariableNames: 'count'`. And each instance of the class `Counter` will have a `count` variable with a _different_ value as we showed in the examples above. Finally the class is defined in the package `'LOOP'`. A package is a kind of folder containing multiple classes. #### Object behavior In addition a class is the place that groups the behavior of its instances. Indeed since all the instances of the class share the _same_ behavior definitions, such behavior is defined and grouped in a class. For counters, the class defines how to retrieve the counter value, how to increment and decrement the count as used in the messages in the previous code snippets. Here is the definition of the **method** `increment`. It simply adds one to the instance variable `count`. ``` Counter >> increment + count := count + 1 ``` When we send a message to a counter for example in the expression `c1 increment`, the method `increment` will be applied on _that_ specific object `c1`. In the expression `c1 increment`, `c1` is called the **receiver** of the message `increment`. In the method `increment`, the variable `count` refers to the variable of the **receiver** of the message. !!coffee A class defines methods that specify the behavior of all the instances created by the class. Multiple methods can accessed to the instance variables of the receiver. For example the methods `increment`, `count:` `decrement` and `printOn:` all access the instance variable `count` of the receiver to perform different computation. ``` Counter >> count: anInteger + count := anInteger ``` ``` Counter >> decrement + count := count - 1 ``` ``` Counter >> printOn: aStream + super printOn: aStream. + aStream nextPutAll: ' with value: ', self count printString. ``` For example, once the following program is executed the count instance variable of the counter `c2` will hold the value 11, since the method `count:` will set its value to 10, and `increment` will set it to 11 and 12 and finally `decrement` will set it to 11. ``` | c2 | +c2 := Counter new. +c2 count: 10. +c2 increment. +c2 increment. +c2 decrement. ``` #### Self is the message receiver Imagine that now we would like to send a message to the object that receives the message itself. We need a way to refer to this object. Pharo defines a special variable for this exact purpose: `self`. !!important `self` always refers to the message receiver that is currently executed. For example we can implement the method `incrementByTwo` as follows: ``` Counter >> incrementByTwo + self increment. + self increment ``` When we execute the expression `c1 incrementByTwo`, during the execution of the method `incrementByTwo`, `self` refers to `c1`. We will explain how a method is found when a message is sent but first we should explain inheritance, i.e., how a class is defined incrementally from a root class and all this will be explained in Chapter *@cha:inheritance@*. ### Class and instances are really different Classes and objects are different objects; they understand different messages. For example, sending `new` to the `Counter` class returns a newly created counter, while sending `new` to a counter results in an error. In the opposite way, sending `increment` to the class `Counter` leads also to an error because the class `Counter` is a factory of objects not the objects themselves. A class is a factory of objects. A class creates instances. An instance does not create other instances of the class. !!coffee A class describes the structure \(instance variables\) and the behavior \(methods\) of _all_ its instances. The state of an instance is the value of its instance variables and it is specific to one single object while the behavior is shared by all the instances of a class. ### Conclusion In this chapter you saw that: - An object is a computer entity that once created receives messages and performs some actions in reaction. - An object has an unique identity. - An object holds some private data that represent its internal state. - A class is a factory of objects: It _describes_ the internal structure of all its instances by means of instance variable. - All objects of the same class share the same behavior, i.e., the same method definitions. - Instance variables are accessible by all the methods of a class. Instance variables have the same lifetime than the object to which they belong to. - In Pharo , instance variables cannot be accessed from outside of an object. Instance variables are only accessible from the methods of the class that define them. - Methods define the behavior of all the instances of the class they belong to. ## Revisiting objects and classes In the previous chapter we presented objects and classes via simple examples. In this chapter we introduce a little bit more elaborated example: a little file system where we revisit everything and extend it to explain _late binding_, _distribution of responsibilities_ and _delegation_. The file example will be extended to present _inheritance_ in Chapter *@cha:inheritance@*. ### A simple and naive file system We start to present a simple example that we use to present and explain the concepts: a simple and naive file system as shown in Figure *@figdirectories@*. What the diagram shows is that we have: - files that also have a name and a contents. Here we get three different files `Babar`, `Astroboy` and `tintinEtLesPicaros`. - directories that have a name and can contain other files or directories. Here we get the `manga`, `comics`, `oldcomics` and `belgiumSchool` directories. Directories can be nested: `comics` contains three repositories. The `belgiumSchool` directory contains `tintinEtLesPicaros`. ![Some directories and files organised in a file system.](figures/comicsFileTree.png width=50&label=figdirectories) ### Studying a first scenario Since what we want to develop may be a bit unclear for us, let us define first an example. In the rest of this book we will code such examples as tests that can automatically be executed. For now it would make the discourse too complex, so we just use little code examples. We create two directories. ``` | dComics dOldComics dManga | +dComics := MFDirectory new name: 'comics'. +dOldComics := MFDirectory new name: 'oldcomics'. ``` We add the oldcomics folder to comics and we check that the parent children relationship is well set. ``` ... +dComics addElement: dOldComics. +dOldComics parent == dComics +>>> true ``` Here we verify that the parent of `dOldComics` is `dComics`: the message ` == ` checks that the receiver is the same object than the argument. You can also inspect the receiver as follows and if you click on the instance variable parent of the receiver you should obtain the situation depicted by Figure *@inspectingComics@*. ``` ... +dOldComics inspect ``` ![Inspecting `dOldComics` and clicking on the `parent` variable. ](figures/inspectingComics.png width=70&label=inspectingComics) We continue with some queries. ``` ... +dComics parent +>>> nil ``` Here we verify that `dOldComics` is comprised in the children of `dComics`. ``` ... +dComics children includes: dOldComics. +>>> true ``` We create a new repository and we check that once added to a parent repository, it is included in the children. ``` dManga := MFDirectory new name: 'manga'. +dComics addElement: dManga. +dComics children includes: dManga +>>> true ``` ![The `Directory` class and some instances \(directories\).](figures/DirectoryAndInstancesV0.pdf width=35&label=directories) ### Defining a class Let us start by defining the directory class. ``` Object subclass: #MFDirectory + instanceVariableNames: 'parent name files' + classVariableNames: '' + package: 'MyFS' ``` When we create a directory, its files is an empty ordered collection. This is what we express in the following method `initialize`. ``` MFDirectory >> initialize + files := OrderedCollection new ``` A newly created object is sent the message `initialize` just after its creation. Therefore the `initialize` method is executed. Now we can write the method `addElement:`. \(To keep things simple, note that we consider that when a file is added to a directory, it was not belonging to a another directory. This behavior could be implemented by `aFile moveTo: aDirectory`\) Adding a file to a directory means: \(1\) that the parent of the file is changed to be the directory to which it is added, \(2\) that the added file is added to the list of files contained in the directory. ``` MFDirectory >> addElement: aFile + aFile parent: self. + files add: aFile ``` Note that the method name `addElement:` is not nice but we chose it on purpose so that you do not believe that delegating requires that the methods have the same name. An object can delegate its part of duties to another object by simply passing a message. We should then define the methods `name:`, `parent:`, `parent`, and `children` to be able to run our example. ``` MFDirectory >> name: aString + name := aString ``` ``` MFDirectory >> parent: aFile + parent := aFile ``` ``` MFDirectory >> parent + ^ parent ``` ``` MFDirectory >> children + ^ files ``` With such method definitions, our little example should run. It should not print the same results because we did not change the printing of the objects yet. #### A first little analysis When we look at the implementation of the method to add a file to a directory we see that the class `MFDirectory` used another class `OrderedCollection` to store the information about the files it contains. An ordered collection is a quite complex object: it can insert, remove elements, grow its size, and many more operations. We say that the class `MFDirectory` delegates a part of its duties \(to keep the information of the files it contains\) to the class `OrderedCollection`. In addition, when an object is executed, the object to which it may delegate part of its computation may change dynamically. Such behavior is not specific to object-oriented programming, in procedural languages we can call another function defined on a data structure. Now with object-oriented programming, there is a really important point: an object will send messages to other objects \(even from the same class\) and such message send will use the message offered by the receiver. There is normally no way for an object to access the internal structure of another object. ### Printing a directory Now we would like to get the directory printed in a better way. Without too much explanation, you should know that the method `printOn: astream` of an object is executed when the system or we send the message `printString` to an object. So we can specialise it. The argument passed to the method `printOn:` is a stream. A stream is an object in which we can store information one after the other in sequence using the message `<<`. The argument of `<<` should be a sequence of objects such as string \(which is a sequence of characters\). ``` MFDirectory >> printOn: aStream + aStream << name ``` Let us try. ``` | el1 el2 | +el1 := MFDirectory new name: 'comics'. +el2 := MFDirectory new name: 'oldcomics'. +el1 addElement: el2. +el1 printString +>>> 'comics' ``` ``` ... +el2 printString +>>> 'oldcomics' ``` What would be nice is to get the full path so that we can immediately understand the configuration. For example we would like to finish with a '/' to indicate that this is a directory as with the ls command on unix. ``` | el1 el2 | +el1 := MFDirectory new name: 'comics'. +el2 := MFDirectory new name: 'oldcomics'. +el1 addElement: el2. +el1 >> printString. +>>>'comics' ``` ``` ... +el2 printString +>>> 'comics/oldcomics/' ``` A possible definition is the following one: ``` MFDirectory >> printOn: aStream + parent isNil + ifFalse: [ parent printOn: aStream ]. + aStream << name. + aStream << '/' ``` ![Navigating an object graph by sending message to different objects. ](figures/InstancesRecursion.pdf width=55&label=InstancesRecursion) Try it and it should print the expected results. What do we see with this definition: it is a kind of recursive definition. The name of a directory is in fact the concatenation \(here we just add in the stream but this is the same. \) of the name of its parents \(as shown in Figure *@InstancesRecursion@*\). Similar to a recursive function navigating a structure composed of similar elements \(like a linked-list or any structure defined by induction\), each parent receives and executes another time the `printOn:` method and returns the name for its part. ### Adding files Now we want to add files. Once we will have defined files we will be able to have a graph of objects of different kinds represent our file system with directories and files as shown in Figure *@Instances@*. ![A graph of objects to represent our file system. ](figures/Instances.pdf width=45&label=Instances) #### An example first Again let us start with an example. A file should contain some contents. ``` | el1 dOldComics | +el1 := MFFile new name: 'astroboy'; contents: 'The story of a boy turned into a robot that saved the world'. +dOldComics := MFDirectory new name: 'oldcomics'. +dOldComics addElement: el1. +el1 printString. +>>> +'oldcomics/astroboy' ``` ![A new class and its instances.](figures/FileV0.pdf width=35&label=FileV0) #### A new class definition Again a file needs a name, a parent and in addition a contents. We define the class `MFFile` as follows and illustrated in Figure *@FileV0@*. Note that this solution is not satisfactory and we will propose a much better one later. ``` Object subclass: #MFFile + instanceVariableNames: 'parent name contents' + classVariableNames: '' + package: 'MyFS' ``` As for the directories we initialize the contents of a file with a default value. ``` MFFile >> initialize + contents := '' ``` We should define the same methods for `parent:`, `parent` and `name:`. This duplication coupled with the fact that we get nearly the same class definition should be a clear warning. It means that we do not reuse enough and that if we want to change the system we will have to change it multiple times and we may introduce errors by forgetting one place. We will address it in Chapter *@cha:inheritance@*. In addition we will add a method to be able to set the contents of the file `contents:`. ``` MFFile >> name: aString + name := aString ``` ``` MFFile >> parent: aFile + parent := aFile ``` ``` MFFile >> parent + ^ parent ``` ``` MFFile >> contents: aString + contents := aString ``` At the stage we should be able to define a file and adding it to a directory. Now we should redefine the implementation of `printOn:` to print nicely the name of file: ``` MFFile >> printOn: aStream + aStream << name ``` But this is not enough because we will just get `'astroboy'` and not `'oldcomics/astroboy'`. So let us improve it. ``` MFFile >> printOn: aStream + parent isNil ifFalse: [ + parent printOn: aStream ]. + aStream << name ``` ![Printing a file: Sending messages inside a graph of different objects. ](figures/InstancesRecursion2.pdf width=60&label=InstancesRecursion2) ### One message and multiple methods Before continuing let us step back and analyse the situation. We send the same messages and we execute different methods. ``` | el1 dOldComics dComics | +el1 := MFFile new name: 'astroboy'; contents: 'The story of a boy turned into a robot that saved the world'. +dOldComics := MFDirectory new name: 'oldcomics'. +dComics := MFDirectory new name: 'comics'. +dComics addElement: dOldComics. +dOldComics addElement: el1. +el1 printString. +>>> +'comics/oldcomics/astroboy' ``` ``` dOldComics printString. +>>> +'comics/oldcomics/' ``` What we see is that there is one message and several implementations of methods and that sending a message will find and execute the correct method. For example, there are two methods `printOn:` one for file and one for directory but only one message `printOn:` sent from the `printString` message. In addition a method can be defined in terms of messages sent to other objects. The method `printOn:` for directories is complex and it delegates the same message to other objects, its parents \(as illustrated by Figure *@InstancesRecursion2@*\). The method `addElement:` delegates to the OrderedCollection sending a different message `add:`. ### Objects: stepping back Now that we saw some examples of objects, it is time to step back. Objects are defined by the values of their state, their behavior \(shared with the other instances of their class\) and an identity. - **State.** Each object has specific values. While all the instances of classes have the same structure, each instance has its own values. Each object has a private state. Clients or users of an object cannot access the state of the object if this one does not explicitly expose it by defining a method returning it \(such as the message `count`\). - **Behavior.** Each object shares the same behavior with all the instances of its class. - **Identity.** An object has an identity. It is unique. `oldcomics` is clearly not the same as `comics`. ### Examples of distribution of responsibilities We will now implement two functionalities: the size of directories and a search based on the contents of the files. This will set the context to explain the key concept of distribution of responsibilities. ![Two classes understanding similar sets of message.](figures/FileDirectoryV1.pdf width=60&label=NutFileDirectoryV1) #### File size Let us imagine that we want to compute the size of a directory. Note that the size computation we propose is fantasist but this is for the sake of the example. To perform such a computation we should also define what is the size of a file. Again let us start with examples \(that you will turn into tests in the future.\). First we define the file size as the size of its name plus the size of its contents. ``` | el | +el := MFFile new name: 'babar'; contents: 'Babar et Celeste'. +el size = 'babar' size + 'Babar et Celeste' size. +>>> true ``` Second we define the directory size as its name size plus the size of its files and we add and arbitrary number: 2. ``` | p2 el | +el := MFFile new name: 'babar'. +p2 := MFDirectory new name: 'oldcomics'. +p2 addElement: el. +p2 size = 'oldcomics' size + 'babar' size + 2 +>>> true ``` We define two methods `size` one for each class \(see Figure *@NutFileDirectoryV1@*\). ``` MFFile >> size + ^ contents size + name size ``` ``` MFDirectory >> size + | sum | + sum := 0. + files do: [ :each | sum := sum + each size ]. + sum := sum + name size. + sum := sum + 2. + ^ sum ``` % For the purists, the previous definition of ==size== can be expressed in fact in one line. % ==inject:into:== % [[[ % MFDirectory >> size % ^ name size + (files inject: 0 into: [:s :e | s + e size ]) + 2 % ]]] #### Search Let us imagine that we want to search the files matching a given string. Here is an example to set the stage. ``` | p el1 el2 | +p := MFDirectory new name: 'comics'. +el1 := MFFile new name: 'babar'; contents: 'Babar et Celeste'. +p addElement: el1. +el2 := MFFile new name: 'astroboy'; contents: 'super cool robot'. +p addElement: el2. +(p search: 'Ba') includes: el1 +>>> true ``` To implement this behavior is quite simple: we define two methods one in each class \(as shown in Figure *@NutFileDirectoryV1@*\). ``` MFFile >> search: aString + ^ '*', aString, '*' match: contents ``` ``` MFDirectory >> search: aString + ^ files select: [ :eachFile | eachFile search: aString ] ``` ### Important points These two examples show several _important_ points: #### Modular thinking Each method is modular in the sense that it only focuses on the behavior of the objects specified by the class defining the method. Such method can be built by sending other messages without having to know how such methods are defined. It also means that we can add a new kind of classes or remove one without having to change the entire system. #### Sending a message is making a choice We send _one_ message and one method amongst the _multiple_ methods with the same name will be selected and executed. The method is dynamically looked up during execution as we will see in Chapters *@cha:inheritance@* and *@cha:messages@*. Sending a message is selecting the corresponding method having the same name than the message. When a message is sent to an object the corresponding method is looked in the class of the message receiver. !!important Sending a message is making a choice. The system selects for us the correct method to be executed. #### Polymorphic objects We created objects \(files and directories\) that are _polymorphic_ in the sense that they offer a common set of messages \(`search:`, `printOn:`, `size`, `parent:`\). This is really powerful because we can compose objects \(for example add a new directory or a file\) without changing the program. Imagine that we add a new kind of directories we can introduce it and reuse extending programs based on `size` or `search:` _without_ changing them. !!important Creating polymorphic objects is a really powerful capability. It lets us extend and change programs without breaking them. Most of the time it is better to give similar name to methods performing similar behavior, and different names when the methods are doing semantically different actions, so that users of the objects are not confused. The polymorphism is really a strength of object-oriented languages because it allows one to treat different objects, i.e., instances of different classes, uniformly as soon as they implement the same messages. Polymorphism works in synergy with the idea that an object is responsible to decide how to react to message reception. Indeed, the fact that different objects can implement the same messages let us write code that only tell the objects to execute some actions without worrying exactly about the kind of objects. ### Distribution of responsibilities This example as well as the printing of files and directories illustrates something fundamental in object-oriented programming: the distribution of responsibilities. With the distribution of responsibilities, each kind of objects is responsible for a specific behavior and a more elaborated behavior is composed out of such different behavior. The size of a directory is computed based on the size of its files by requesting the files to compute their size. #### Procedural Let us take some time to compare with procedural thinking. Computing the size of a list of files and directories would have been expressed as a monolitic behavior sketch below: ``` sizeOfFiles: files + | sum | + sum := 0. + files do: [ :aFile | + aFile class = MFFile + ifTrue: [ sum := sum + aFile name size + aFile contents size ]. + aFile class = MFDirectory + ifTrue: [ + | fileSum | + fileSum := 0. + each files do: [:anInsideFile | fileSum := fileSum + anInsideFile name size + anInsideFile contents size ]. + sum := sum + fileSum + each name size + 2]. + ^ sum ``` While this example is a bit exagerated, we see several points: - First, we explicitly check the kind of structures we are manipulating. If this is a file or directory we do something different. - Second, the logic of the computation is defined inside the `sizeOfFiles:` itself, and not in the entities themselves. This means in particular that such logic cannot be reused. - A part of the implementation logic is exposed and not in control of the object. It means that if we decide to change the internal structure of our classes, we will have to change this function too. - Adding a new kind of such as a root directory is not modular. We will have to modify the method `sizeOfFiles:` function. What you should also see when you compare the two versions is that in the procedural version we have to check the kind of object we manipulate. In the object-oriented version, we simply tell the object to perform its own computation and return the result to us. !!important Don't ask, tell. Object-oriented programming essence is about sending order not checking state. ### So far so good? Not fully! We have a system with two classes and it offers some behavior composed out of well defined local behavior \(see Figure *@NutFileDirectoryV1@*\). We can have objects composed out of other objects and messages flow within the graph. Object-oriented programming could stop here. Now it is annoying to have to duplicate structure and some methods between files and directories and this is what we will see when we will look at inheritance in Chapter *@cha:inheritance@*. Inheritance is a mechanism to specialize incrementally classes from other classes. ### Conclusion - A class describes the state \(instance variables\) and the behavior \(methods\) of all its instances. The state of an instance is the value of its instance variables and it is specific to one single object while the behavior is shared by all the instances of a class. - Different objects, instances of different classes, can react differently to the same messages. - When sending a message, the associated method is found and executed. \ No newline at end of file diff --git a/Chapters/ObjectsAndClasses/ObjectsAndClasses.md b/Chapters/ObjectsAndClasses/ObjectsAndClasses.md new file mode 100644 index 0000000..e69de29 diff --git a/Chapters/PaperStoneScissor/PaperStoneScissor.md b/Chapters/PaperStoneScissor/PaperStoneScissor.md new file mode 100644 index 0000000..9db29c4 --- /dev/null +++ b/Chapters/PaperStoneScissor/PaperStoneScissor.md @@ -0,0 +1,44 @@ +## Stone Paper Scissors @cha_stone As we already saw sending a message is in fact making a choice. Indeed when we send a message, the method associated with the method in the class hierarchy of the receiver will be selected and executed. Now we often have cases where we would like to select a method based on the receiver of the message and one argument. Again there is a simple solution named double dispatch that consists in sending another message to the argument hence making two choices one after the other. This technique while simple can be challenging to grasp because programmers are so used to think that choices are made using explicit conditionals. In this chapter we will show an example of double dispatch via the paper stone scissors game. ### Starting with a couple of tests ``` TestCase subclass: #StonePaperScissorsTest + instanceVariableNames: '' + classVariableNames: '' + package: 'StonePaperScissors' ``` ``` StonePaperScissorsTest >> testPaperIsWinning + self assert: (Stone new play: Paper new) = #paper ``` ``` StonePaperScissorsTest >> testScissorIsWinning + self assert: (Scissors new play: Paper new) = #scissors ``` ``` StonePaperScissorsTest >> testStoneAgainsStone + self assert: (Stone new play: Stone new) = #draw ``` ### Creating the classes ``` Object subclass: #Paper + instanceVariableNames: '' + classVariableNames: '' + package: 'StonePaperScissors' ``` ``` Object subclass: #Scissors + instanceVariableNames: '' + classVariableNames: '' + package: 'StonePaperScissors' ``` ``` Object subclass: #Stone + instanceVariableNames: '' + classVariableNames: '' + package: 'StonePaperScissors' ``` They could share a common superclass ### With messages ``` StonePaperScissorsTest >> testPaperIsWinning + self assert: (Stone new play: Paper new) = #paper ``` ``` Stone >> play: anotherTool + ^ anotherTool playAgainstStone: self ``` ``` Paper >> playAgainstStone: aStone + ^ #paper ``` The test should pass now. #### playAgainstStone: ``` Scissors >> playAgainstStone: aStone + ^ #stone ``` ``` Stone >> playAgainstStone: aStone + ^ #draw ``` #### Scissors now ``` StonePaperScissorsTest >> testScissorIsWinning + self assert: (Scissors new play: Paper new) = #scissors ``` ``` Scissors >> play: anotherTool + ^ anotherTool playAgainstScissors: self ``` ``` Scissors >> playAgainstScissors: aScissors + ^ #draw ``` ``` Paper >> playAgainstScissors: aScissors + ^ #scissors ``` ``` Stone >> playAgainstScissors: aScissors + ^ #stone ``` #### Paper now ``` Paper >> play: anotherTool + ^ anotherTool playAgainstPaper: self ``` ``` Scissors >> playAgainstPaper: aPaper + ^ #scissors ``` ``` Paper >> playAgainstPaper: aPaper + ^ #draw ``` ``` Stone >> playAgainstPaper: aPaper + ^ #paper ``` ![An overview of a possible solution using double dispatch.](figures/StonePaperScissors.pdf width=80) The methods could return a value such as 1 when the receiver wins, 0 when there is draw and -1 when the receiver loses. Add new tests and check this version. ### A Better API Both previous approaches either returning a symbol or a number are working but we can ask ourselves how the client will use this code. Most of the time he will have to check again the returned result to perform some actions. ``` (aGameElement play: anotherGameElement) = 1 + ifTrue: [ do something for aGameElement] + (aGameElement play: anotherGameElement) = -1 ``` So all in all, while this was a good exercise to help you understand that we do not need to have explicit conditionals and that we can use message passing instead, it felt a bit disappointing. But there is a much better solution using double dispatch. The idea is to pass the action to be executed to the object and that the object decide what to do. ``` Paper new competeWith: Paper new + onDraw: [ Game incrementDraw ] + onReceiverWin: [ ] + onReceiverLose: [ ] ``` ``` Paper new competeWith: Stone new + onDraw: [ ] + onReceiverWin: [ Game incrementPaper ] + onReceiverLose: [ ] ``` Propose an implementation. ### A possible implementation ``` Paper >> play: anElement onDraw: aDrawBlock onWin: aWinBlock onLose: aLoseBlock + ^ anElement + playAgainstPaper: self + onDraw: aDrawBlock + onReceiverWin: aWinBlock + onReceiverLose: aLoseBlock ``` ``` Paper >> playAgainstPaper: anElement onDraw: aDrawBlock onReceiverWin: aWinBlock onReceiverLose: aLoseBlock + ^ aDrawBlock value ``` ### Conclusion Sending a message is making a choice amongst several methods. Depending on the receiver of a message the correct method will be selected. Therefore sending a message is making a choice and the different classes represent the possible alternatives. Now this example illustrates this point but going even further. Here we wanted to be able to make a choice depending on both an object and the argument of the message. The solution shows that it is enough to send back another message to the argument to perform a second selection that because of the first message now realizes a choice based on a message receiver and its argument. \ No newline at end of file diff --git a/Chapters/PlayingWithTurtles/PlayingWithTurtles.md b/Chapters/PlayingWithTurtles/PlayingWithTurtles.md new file mode 100644 index 0000000..cc5d3df --- /dev/null +++ b/Chapters/PlayingWithTurtles/PlayingWithTurtles.md @@ -0,0 +1 @@ +## Playing with Objects @cha:PlayingWithObjects ### Turtle first Here I want the reader to - create a turtle - send messages - create a method - send message - create two turtles ### Counter To the same with Counter - create a counter - send messages - create a method - create another counter ### Everything is an object ### Let us study a script ### Objects ### blabla.... \ No newline at end of file diff --git a/Chapters/QRCode/QRCode.md b/Chapters/QRCode/QRCode.md new file mode 100644 index 0000000..287735e --- /dev/null +++ b/Chapters/QRCode/QRCode.md @@ -0,0 +1 @@ +Gofer new smalltalkhubUser: 'JochenRick' project: 'QRCode'; version: 'QRCode-JochenRick.2'; update. qrcode := 'http://car.mines-douai.fr/luc' asQRCode. qrcode backgroundColor: Color white. qrcode foregroundColor: Color black. \(qrcode formWithQuietZone magnifyBy: 15\) asMorph openInWorld. \ No newline at end of file diff --git a/Chapters/SimpleLAN/SimpleLAN.md b/Chapters/SimpleLAN/SimpleLAN.md new file mode 100644 index 0000000..57ea7dd --- /dev/null +++ b/Chapters/SimpleLAN/SimpleLAN.md @@ -0,0 +1,315 @@ +## A simple network simulator @ch:lan In this chapter we will develop a simulator for a computer network, from scratch, step by step. The program starts with a simplistic model of a computer network, made of objects that represent different parts of a local network such as packets, nodes, links, workstations, and hubs. At first we will just simulate the different steps of packet delivery and have fun with the system. As a second step, we will extend the basic functionalities by adding extensions such as a hub and different packet routing strategies. In doing so we will revisit many object-oriented concepts such as polymorphism, encapsulation, and hooks and templates. Finally, this system could be further refined to become an experimentation platform to explore and understand networking and distributed algorithms. ![Two little networks composed of nodes and sending packets over links.](figures/lan-overview.pdf width=80) #### Basic definitions and a starting point We need to establish a basic mental model, so what does the description above tell us about a network? A _network_ is a number of interconnected _nodes_, which exchange _data packets_. We will therefore probably need to model the nodes, the connection links, and the packets. Let's fill out a little more detail: - Nodes have _addresses_, and can send and receive packets. - _Links_ connect two nodes together, and transmit the packets between them. - A _packet_ transports a payload and includes the address of the node to which it should be delivered; if we want nodes to be able to answer \(after they receive the packet\), packets should also have the address of the node which originally sent it. ### Packets are simple value objects Packets seem to be the simplest objects in our model: we only need to create them, ask them about the data they contain, and... that's about it. Once created, a packet object is a passive data structure; it will not change its data, knows nothing of the surrounding network, and has no real behavior to speak of. This sort of object is often referred to as a _value object_. Let's start by defining a test class and a first test, sketching out what creating and looking at packets should look like: ```language=smalltalk TestCase subclass: #KANetworkEntitiesTest + instanceVariableNames: '' + classVariableNames: '' + category: 'NetworkSimulator-Tests' ``` ```language=smalltalk KANetworkEntitiesTest >> testPacketCreation + | src dest payload packet | + src := Object new. + dest := Object new. + payload := Object new. + + packet := KANetworkPacket from: src to: dest payload: payload. + + self assert: packet sourceAddress equals: src. + self assert: packet destinationAddress equals: dest. + self assert: packet payload equals: payload ``` By writing this unit test, we have described how we think packets should be created, using a `from:to:payload:` constructor message, and how they should be accessed, using three messages: `sourceAddress`, `destinationAddress`, and `payload`. Since we have not yet decided what addresses and payloads should look like, we will just pass arbitrary objects as parameters. All that matters is that when we tell the packet to give us one of those three pieces of information, it returns the correct object back. Of course, if we compile and run this test method, it will fail because the class `KANetworkPacket` has not been created yet, nor have any of the four above messages. You can either execute the test and let the system prompt you to define the missing objects and messages when needed, or we can define the class from the start: ```language=smalltalk Object subclass: #KANetworkPacket + instanceVariableNames: 'sourceAddress destinationAddress payload' + classVariableNames: '' + category: 'NetworkSimulator-Core' ``` The class-side constructor method creates an instance which it returns after sending it an initialization message, so nothing too original as far as constructors go: ```language=smalltalk KANetworkPacket class >> from: sourceAddress to: destinationAddress payload: anObject + ... Your code ... ``` That constructor will need to pass the initialization parameters to the new instance. It's preferable to define a single initialization method that takes all needed parameters at once, since it is only supposed to be called when creating packets and should not be confused with a setter: ```language=smalltalk KANetworkPacket >> initializeSource: *sourceAddress destination: destinationAddress payload: aPayload + ... Your code ... ``` Once a packet is created, all we need to do with it is to obtain its payload, or the addresses of its source or destination nodes. Define the following getters: ```language=smalltalk KANetworkPacket >> sourceAddress + ... Your code ... +KANetworkPacket >> destinationAddress + ... Your code ... +KANetworkPacket >> payload + ... Your code ... ``` Now our test should be running and passing. That's enough for our admittedly simplistic model of packets. We've completely ignored the layers of the [OSI model](https://en.wikipedia.org/wiki/OSI_model), but it could be an interesting exercise to model them more precisely. ### Nodes are known by their address The first obvious thing we can say about a network node is that if we want to be able to send packets to it, then it should have an address. Let's translate that into a test: ```language=smalltalk KANetworkEntitiesTest >> testNodeCreation + | address node | + address := Object new. + node := KANetworkNode withAddress: address. + self assert: node address equals: address ``` Like before, to run this test to completion, we will have to define the `KANetworkNode` class: ```language=smalltalk Object subclass: #KANetworkNode + instanceVariableNames: 'address' + classVariableNames: '' + category: 'NetworkSimulator-Core' ``` Then a class-side constructor method taking the address of the new node as its parameter: ```language=smalltalk KANetworkNode class >> withAddress: aNetworkAddress + ^ self new + initializeAddress: aNetworkAddress ``` The constructor relies on an instance-side initialization method, and the test asserts that the `address` accessor works as expected. Let's define them: ```language=smalltalk KANetworkNode >> initializeAddress: aNetworkAddress + ... Your code ... ``` ``` KANetworkNode >> address + ... Your code ... ``` Again, our tests should now pass. ### Links are one-way connections between nodes After nodes and packets, what about looking at links? In the real world, network cables are bidirectional, but that's because they have wires going both ways. Here, we're going to keep it simple and define links as simple one-way connections; to make a two-way connection, we will just use two links, one in each direction. However, creating links that know their source and destination node is not sufficient: nodes also need to know about their outgoing links, otherwise they cannot send packets. Let's write a test to cover this. ```language=smalltalk KANetworkEntitiesTest >> testNodeLinking + | node1 node2 link | + node1 := KANetworkNode withAddress: #address1. + node2 := KANetworkNode withAddress: #address2. + link := KANetworkLink from: node1 to: node2. + + link attach. + + self assert: (node1 hasLinkTo: node2) ``` This test creates two nodes and a link. After telling the link to _attach_ itself, we check that it did so: the source node should confirm that it has an outgoing link to the destination node. Note that the constructor could have registered the link with `node1`, but we opted for a separate message `attach` instead, because it's bad practice to have a constructor change other objects. This way we can build links between arbitrary nodes and still have control of when the connection really becomes part of the network model. For symmetry, we could have specified that `node2` has an incoming link from `node1`, but that ends up not being necessary, so we leave that out for now. Again, we need to define the class of links: ```language=smalltalk Object subclass: #KANetworkLink + instanceVariableNames: 'source destination' + classVariableNames: '' + category: 'NetworkSimulator-Core' ``` A constructor that passes the two required parameters to an instance-side initialization message: ```language=smalltalk KANetworkLink class >> from: sourceNode to: destinationNode + ^ self new + initializeFrom: sourceNode to: destinationNode ``` As well as the initialization method and accessors: ```language=smalltalk KANetworkLink >> initializeFrom: sourceNode to: destinationNode + ... Your code ... +KANetworkLink >> source + ... Your code ... +KANetworkLink >> destination + ... Your code ... ``` The `attach` method of a link should not \(and cannot\) directly modify the source node, so it must delegate to it instead. ```language=smalltalk KANetworkLink >> attach + source attach: self ``` This is an example of separation of concerns: the link knows which node has to do what, but only the node itself knows precisely how to do that. If a node knows about all its outgoing links then it means it has a collection of them, and attaching a link adds it to that collection: ```language=smalltalk KANetworkNode >> attach: anOutgoingLink + outgoingLinks add: anOutgoingLink ``` For this method to compile correctly, we will need to extend `KANetworkNode` with the new instance variable `outgoingLinks`, and with the corresponding initialization code: ```language=smalltalk KANetworkNode >> initialize + outgoingLinks := Set new. ``` And finally the unit test relied on a predicate method which we define in `KANetworkNode`: ```language=smalltalk KANetworkNode >> hasLinkTo: anotherNode + ... Your code ... ``` The method `hasLinkTo:` should verify that there is at least one outgoing link whose destination is the node passed in as an argument. We suggest to have a look at the iterator `anySatisfy:` to express this logic. Again, all the tests should now pass. ![Current API of our three main classes.](figures/API1.pdf width=80) ### Making our objects more understandable When programming we often make mistakes and it is important to help developers to address them. Let us put a breakpoint in to try and understand the objects we have been working with: ```language=smalltalk KANetworkEntitiesTest >> testNodeLinking + | node1 node2 link | + node1 := KANetworkNode withAddress: #address1. + node2 := KANetworkNode withAddress: #address2. + link := KANetworkLink from: node1 to: node2. + link attach. + self halt. + self assert: (node1 hasLinkTo: node2) ``` Running the test will open a debugger as the one shown in Figure *@debugger@*. We can see objects, but their textual representation is too generic to be of any help. ![Navigating specific objects having a generic presentation.](figures/debugger.png width=100&label=debugger) The method `printOn:` is responsible for printing the object's representation. So we should redefine this method for our objects: ```language=smalltalk KANetworkNode >> printOn: aStream + aStream nextPutAll: 'Node ('. + aStream nextPutAll: address , ')' ``` ```language=smalltalk KANetworkLink >> printOn: aStream + aStream nextPutAll: 'Link'. + source + ifNotNil: [ aStream + nextPutAll: ' '; + nextPutAll: source address ]. + destination + ifNotNil: [ aStream + nextPutAll: ' -> '; + nextPutAll: destination address ] ``` Now if we rerun the test we obtain a better user experience as shown in Figure *@debuggerXP@*: we can see the address of a node and the source and destination of a link. ![Navigating objects offering a customized presentation.](figures/betterDebugerXP.png width=100&label=debuggerXP) ### Simulating the steps of packet delivery The next big feature is that nodes should be able to send and receive packets, and links should be able to transmit them. ```language=smalltalk KANetworkEntitiesTest >> testSendAndTransmit + | srcNode destNode link packet | + srcNode := KANetworkNode withAddress: #src. + destNode := KANetworkNode withAddress: #dest. + link := (KANetworkLink from: srcNode to: destNode) attach; yourself. + packet := KANetworkPacket from: #address to: #dest payload: #payload. + + srcNode send: packet via: link. + self assert: (link isTransmitting: packet). + self deny: (destNode hasReceived: packet). + + link transmit: packet. + self deny: (link isTransmitting: packet). + self assert: (destNode hasReceived: packet) ``` We create and setup two nodes, a link between them, and a packet. Now, to control which packets get delivered in which order, we specify that it happens in separate, controlled steps. This will allow us to model packet delivery precisely, to simulate latency, out-of-order reception, etc. - First, we tell the node to send the packet using the message `send:via:`. At that point, the packet should be passed to the link for transmission, but not be delivered yet. - Then, we tell the link to actually transmit the packet along using the message `transmit:`, and thus the packet should be received by the destination node. ### Sending a packet To send a packet, the node emits it on the link: ```language=smalltalk KANetworkNode >> send: aPacket via: aLink + aLink emit: aPacket ``` For the simulation to be realistic, we do not want the packet to be delivered right away. Instead, emitting a packet just stores it in the link until the user selects this packet be transmitted using the `transmit:` message. Storing packets requires adding an instance variable to `KANetworkLink`, as well as specifying how this instance variable should be initialized. ```language=smalltalk Object subclass: #KANetworkLink + instanceVariableNames: 'source destination packetsToTransmit' + classVariableNames: '' + category: 'NetworkSimulator-Core' ``` ```language=smalltalk KANetworkLink >> initialize + packetsToTransmit := OrderedCollection new ``` ```language=smalltalk KANetworkLink >> emit: aPacket + "Packets are not transmitted right away, but stored. + Transmission is explicitly triggered later by sending #transmit:." + + packetsToTransmit add: aPacket ``` We also add a testing method to check whether a given packet is currently being transmitted by a link: ```language=smalltalk KANetworkLink >> isTransmitting: aPacket + ... Your code ... ``` ### Transmitting across a link Transmitting a packet means telling the link's destination node to receive it. Nodes only consume packets addressed to them; fortunately this is what will happen in our test, so we can worry about the alternative case later \(`notYetImplemented` is a special message that we can use in place of code that we will have to write eventually, but prefer to ignore for now\). ```language=smalltalk KANetworkNode >> receive: aPacket from: aLink + aPacket destinationAddress = address + ifTrue: [ + self consume: aPacket. + arrivedPackets add: aPacket ] + ifFalse: [ self notYetImplemented ] ``` Consuming a packet represents what the node will do with it at the application level; for now let's just define an empty `consume:` method, as a placeholder: ```language=smalltalk KANetworkNode >> consume: aPacket + "Default handling is to do nothing." ``` After consuming the packet, we note that it did arrive; this is mostly for testing and debugging, but someday we might want to simulate packet losses and re-emissions. Don't forget to declare and initialize the `arrivedPackets` instance variable, along with its accessor: ```language=smalltalk KANetworkNode >> hasReceived: aPacket + ... Your code ... ``` Now we can implement the `transmit:` message. A link can not transmit packets that have not been sent via it, and once transmitted, the packet should not be in the link anymore. We should remove it from the link's list of packets to be transmitted and tell the destination to receive it using the message `receive:from:`. ```language=smalltalk KANetworkLink >> transmit: aPacket + "Transmit aPacket to the destination node of the receiver link." + ... Your code ... ``` At this point all our tests should pass. Note that the message `notYetImplemented` is not called, since our tests do not yet require routing. Figure *@Api2@* shows that the API of our classes is getting richer than before. ![Richer API.](figures/API2.pdf width=90&label=Api2) ### The loopback link On a real network, when a node wants to send a packet to itself, it does not need any connection to do so. In real-world networking stacks, loopback routing shortcuts the lower networking layers, but this is more detail than we are modeling here. Still, we do want to model the fact that the loopback link is a little special, so each node will store its own loopback link, separately from the outgoing links. We start to define a test. ```language=smalltalk KANetworkEntitiesTest >> testLoopback + | node packet | + node := KANetworkNode withAddress: #address. + packet := KANetworkPacket from: #address to: #address payload: #payload. + + node send: packet. + node loopback transmit: packet. + + self assert: (node hasReceived: packet). + self deny: (node loopback isTransmitting: packet) ``` The loopback link is implicitely created as part of the node itself. We also introduce a new `send:` message, which takes the responsibility of selecting the link to emit the packet. For triggering packet transmission, we have to use a specific accessor get to the loopback link of the node. First, we have to add yet another instance variable in nodes: ```language=smalltalk Object subclass: #KANetworkNode + instanceVariableNames: 'address outgoingLinks loopback arrivedPackets' + classVariableNames: '' + category: 'NetworkSimulator-Core' ``` As with all instance variables, we have to remember to make sure it is correctly initialized; we thus modify `initialize`: ```language=smalltalk KANetworkNode >> initialize + ... Your code ... ``` The accessor has nothing special: ```language=smalltalk KANetworkNode >> loopback + ^ loopback ``` And finally we can focus on the `send:` method and automatic link selection. The method `send:` should be more generic than the method `send:via:` and will be one exposed as a public entry point. This method has to rely on some routing algorithm to identify which links will transmit the packet closer to its destination. Since some routing algorithms select more than one link, we will implement routing as an _iteration_ method, which evaluates the given block for each selected link. ```language=smalltalk KANetworkNode >> send: aPacket + "Send aPacket, leaving the responsibility for routing to the node." + self + linksTowards: aPacket destinationAddress + do: [ :link | self send: aPacket via: link ] ``` One of the simplest routing algorithms is _flooding_, which just sends the packet to every single outgoing link. Obviously this is a waste of bandwidth, but it works without any knowledge of the network topology beyond the list of outgoing links. There is, however, one case where we know how to route the packet: if the destination address matches the one of the current node, we can select the loopback link. The logic of `linksTowards:do:` should look something like: - compare the packet's destination address with the one of the node - if it is the same, we execute the block using the loopback link - else we simply iterate on the outgoing links of the receiver. ```language=smalltalk KANetworkNode >> linksTowards: anAddress do: aBlock + "Simple flood algorithm: route via all outgoing links. + However, just loopback if the receiver node is the routing destination." + ... Your code ... ``` Now we have the basic model working we can try more realistic examples. ### Modeling the network itself More realistic tests will require non-trivial networks. We therefore need an object that represents the network as a whole, to avoid keeping many nodes and links in individual variables. We will introduce a new class `KANetwork`, whose responsibility is to help us build, assemble then find the different nodes and links involved in a network. Let's start by creating another test class, to keep things in order: ```language=smalltalk TestCase subclass: #KANetworkTest + instanceVariableNames: 'net hub alone' + classVariableNames: '' + category: 'NetworkSimulator-Tests' ``` Since every test will need to rebuild the example network from scratch, we'll move those steps out to a method called `buildNetwork`. We'll then send that message in the `setUp` method of our test, which is called before each test method is run. ```language=smalltalk KANetworkTest >> setUp + super setUp. + self buildNetwork ``` We'll worry about the implementation of `buildNetwork` shortly. Before we go any further with the test setup, let's write a test that will pass once we've made progress; we want to be able to access network nodes given their addresses. Here we check that we get a the node `hub` based on its address: ```language=smalltalk KANetworkTest >> testNetworkFindsNodesByAddress + self + assert: (net nodeAt: hub address ifNone: [ self fail ]) + equals: hub ``` We will have to implement `nodeAt:ifNone:` on our `KANetwork` class, but first we need to decide how we're going to build our example network. This is what it's going to look like: - The nodes `pc1`, `pc2`, `mac`, and `impr` connected in a star-shape around a central `hub` node - A pair of connected nodes, `ping` and `pong`, which are part of the network but not linked to any other nodes - And the node `alone` which sits all by itself, not even a part of our network We can picture this as in Figure *@net@*. ![Our network `net`.](figures/lan-star.pdf width=50&label=net) Expanding a network implies adding new connections and possibly new nodes to it. If the `net` object understands a `connect: aNode to: anotherNode` message, we should be able to build nodes and connect them into a network that matches the figure. This can then be the content of our `buildNetwork` method: ```language=smalltalk KANetworkTest >> buildNetwork + alone := KANetworkNode withAddress: #alone. + net := KANetwork new. + hub := KANetworkNode withAddress: #hub. + #( mac pc1 pc2 impr ) do: [ :addr | + | node | + node := KANetworkNode withAddress: addr. + net connect: node to: hub ]. + net + connect: (KANetworkNode withAddress: #ping) + to: (KANetworkNode withAddress: #pong) ``` The name of the `connect:to:` message suggests that establishing the bidirectional links is the responsibility of the `net` object. It also has to remember enough information to allow us to inspect the network topology; we can simply store nodes and links in a couple of sets, even though that representation is a little redundant. Let's define the class with two instance variables: ```language=smalltalk Object subclass: #KANetwork + instanceVariableNames: 'nodes links' + classVariableNames: '' + category: 'NetworkSimulator-Core' ``` Whenever we define an instance variable, initialization \(if any\) comes next: ```language=smalltalk KANetwork >> initialize + ... Your code ... ``` Now we should give the network the ability to create links. We'll use this method to add links to the network. ``` KANetwork >> makeLinkFrom: aNode to: anotherNode + ^ KANetworkLink from: aNode to: anotherNode ``` We will also add a low level method `add:` to add a node in a network: ```language=smalltalk KANetwork >> add: aNode + nodes add: aNode ``` And to test the network construction we add a little test method: ```language=smalltalk KANetwork >> doesRecordNode: aNode + ^ nodes includes: aNode ``` Now we can add isolated nodes to the network. #### Connecting nodes. Connecting nodes without ensuring that they are part of the network doesn't make sense. Therefore, when connecting nodes, we will also: - ensure that nodes are added to the network \(by adding them in the node Set of the network\) - create and attach links to the nodes in _both_ directions - and finally store both links in the network Here is a test covering connecting nodes in a network: ``` KANetworkTest >> testConnect + | netw hubb mac pc1 | + netw := KANetwork new. + hubb := KANetworkNode withAddress: #hub. + mac := KANetworkNode withAddress: #mac. + pc1 := KANetworkNode withAddress: #pc1. + + netw connect: hubb to: mac. + self assert: (hubb hasLinkTo: mac). + self assert: (mac hasLinkTo: hubb). + self assert: (netw doesRecordNode: hubb). + self assert: (netw doesRecordNode: mac). + + netw connect: hubb to: pc1. + self assert: (hubb hasLinkTo: pc1). + self assert: (mac hasLinkTo: hubb) ``` Now implement the `connect:to:` method. For concision, note that the `attach` method we defined previously effectively returns the link. ```language=smalltalk KANetwork >> connect: aNode to: anotherNode + ... Your code ... ``` The test `testConnect` should now be green. ### Looking up nodes At this point, the test `testNetworkFindsNodesByAddress` should run through `setUp` method, but fail in the unit test itself. This is because we still need to implement node lookup. The base lookup should find the first node that has the requested address, or evaluate a fall-back block \(a perfect case for the `detect:ifNone:` message\): ```language=smalltalk KANetwork >> nodeAt: anAddress ifNone: noneBlock + ... Your code ... ``` We can also make a convenience method, `nodeAt:`, for node lookup. This will raise the predefined `NotFound` exception if it does not find the node. Let's first write a test which validates this behavior: ```language=smalltalk KANetworkTest >> testNetworkOnlyFindsAddedNodes + self + should: [ net nodeAt: alone address ] + raise: NotFound ``` Then we can simply express `nodeAt:` by delegating to `nodeAt:ifNone:`. Note that to raise an exception, you can simply send the message `signal` to the exception class. Here we use the specific class method `signalFor:in:` defined on the `NotFound` class. `NotFound` exceptions are used when a collection does not contain an object; the `signalFor:in:` method adds both the collection and the missing object to the exception, generating a more detailed description of the problem for the user: ```language=smalltalk KANetwork >> nodeAt: anAddress + ^ self + nodeAt: anAddress + ifNone: [ NotFound signalFor: anAddress in: self ] ``` ### Looking up links Next, we want to be able to lookup links between two nodes. Again we define a new test: ```language=smalltalk KANetworkTest >> testNetworkFindsLinks + | link | + self + shouldnt: [ link := net linkFrom: #pong to: #ping ] + raise: NotFound. + self + assert: link source + equals: (net nodeAt: #pong). + self + assert: link destination + equals: (net nodeAt: #ping) ``` And we define the method `linkFrom:to:`, returning the link between source and destination nodes with matching addresses, and signalling `NotFound` if no such link is found: ```language=smalltalk KANetwork >> linkFrom: sourceAddress to: destinationAddress + ... Your code ... ``` #### Final check. As a final check, let's try some of the previous tests, first on the isolated `alone` node, showing that loopback works even without a network connection: ```language=smalltalk KANetworkTest >> testSelfSend + | packet | + packet := KANetworkPacket + from: alone address + to: alone address + payload: #something. + self assert: (packet isAddressedTo: alone). + self assert: (packet isOriginatingFrom: alone). + + alone send: packet. + self deny: (alone hasReceived: packet). + self assert: (alone loopback isTransmitting: packet). + + alone loopback transmit: packet. + self deny: (alone loopback isTransmitting: packet). + self assert: (alone hasReceived: packet) ``` You can see that we used new convenience testing methods `isAddressedTo:` and `isOriginatingFrom:` which help inspect the state of a simulated network without explicitly comparing addresses. However, those methods should not take part in network simulation code, since in the real world nodes can never know their peers other than through their addresses. ```language=smalltalk KANetworkPacket >> isAddressedTo: aNode + ^ destinationAddress = aNode address ``` ```language=smalltalk KANetworkPacket >> isOriginatingFrom: aNode + ^ sourceAddress = aNode address ``` The second test attempts transmitting a packet in the network, between the directly connected nodes `ping` and `pong`: ```language=smalltalk KANetworkTest >> testDirectSend + | packet ping pong link | + packet := KANetworkPacket from: #ping to: #pong payload: #ball. + ping := net nodeAt: #ping. + pong := net nodeAt: #pong. + link := net linkFrom: #ping to: #pong. + + ping send: packet. + self assert: (link isTransmitting: packet). + self deny: (pong hasReceived: packet). + + link transmit: packet. + self deny: (link isTransmitting: packet). + self assert: (pong hasReceived: packet) ``` Both tests should pass with no additional work, since they just reproduce what we already tested in `KANetworkEntitiesTest` and adding `KANetwork` did not impact the established behavior of nodes, links, and packets. ### Packet delivery with forwarding Up until now, we have only tested packet delivery between directly connected nodes; now let's try sending to a node so that the packet has to be forwarded through the `hub`. ```language=smalltalk KANetworkTest >> testSendViaHub + | hello mac pc1 firstLink secondLink | + hello := KANetworkPacket from: #mac to: #pc1 payload: 'Hello!'. + mac := net nodeAt: #mac. + pc1 := net nodeAt: #pc1. + firstLink := net linkFrom: #mac to: #hub. + secondLink := net linkFrom: #hub to: #pc1. + + self assert: (hello isAddressedTo: pc1). + self assert: (hello isOriginatingFrom: mac). + + mac send: hello. + self deny: (pc1 hasReceived: hello). + self assert: (firstLink isTransmitting: hello). + + firstLink transmit: hello. + self deny: (pc1 hasReceived: hello). + self assert: (secondLink isTransmitting: hello). + + secondLink transmit: hello. + self assert: (pc1 hasReceived: hello). ``` If you run this test, you will see that it fails because of the `notYetImplemented` message we left earlier in `receive:from:`; it's time to fix that! When a node receives a packet, but it is not the recipient, it should forward the packet: ```language=smalltalk KANetworkNode >> receive: aPacket from: aLink + aPacket destinationAddress = address + ifTrue: [ + self consume: aPacket. + arrivedPackets add: aPacket ] + ifFalse: [ self forward: aPacket from: aLink ] ``` Now we need to implement packet forwarding, but there is a trap... An easy solution would be to simply `send:` the packet again: the hub would send the packet to all its connected nodes, one of which happens to be `pc1`, the recipient, so all is good! _Wrong!_ The packet would be also sent to other nodes than the recipient. What would those nodes do when they receive a packet not addressed to them? Forward it. Where? To all their neighbours, which would forward it again... and again... and again... so when would the forwarding stop? We should improve our test to demonstrate that hubs don't send the same packet back to the originating node: ```language=smalltalk KANetworkTest >> testSendViaHub + | hello mac pc1 firstLink secondLink | + hello := KANetworkPacket from: #mac to: #pc1 payload: 'Hello!'. + mac := net nodeAt: #mac. + pc1 := net nodeAt: #pc1. + firstLink := net linkFrom: #mac to: #hub. + secondLink := net linkFrom: #hub to: #pc1. + backLink := net linkFrom: #hub to: #mac. + + self assert: (hello isAddressedTo: pc1). + self assert: (hello isOriginatingFrom: mac). + + mac send: hello. + self deny: (pc1 hasReceived: hello). + self assert: (firstLink isTransmitting: hello). + + firstLink transmit: hello. + self deny: (pc1 hasReceived: hello). + self deny: (backLink isTransmitting: hello). + self assert: (secondLink isTransmitting: hello). + + secondLink transmit: hello. + self assert: (pc1 hasReceived: hello). ``` The link `backLink` monitors whether `hub` sends the `hello` packet back to `mac` when it forwards the message upon its receipt. To get this test to pass we need hubs to behave differently from nodes. In reality hubs work at the lower layers of the OSI model, but our simplified model does not have that level of detail. We can approximate this by saying that upon reception of a packet addressed to another node, a hub should forward the packet, but a normal node should just ignore it. Let's first define an empty `forward:from:` method for nodes, then add a new class for hubs, which will be modeled as nodes with an actual implementation of forwarding: ```language=smalltalk KANetworkNode >> forward: aPacket from: arrivalLink + "Do nothing. Normal nodes do not route packets." ``` ### Introducing a new kind of node Now we define the class `KANetworkHub` that will be the recipient of hub specific behavior: ```language=smalltalk KANetworkNode subclass: #KANetworkHub + instanceVariableNames: '' + classVariableNames: '' + category: 'NetworkSimulator-Core' ``` We can also create this class through the _Refactorings_ menu option on the `KANetworkNode` class. To do this we open the menu, select _New subclass_ and then enter the new subclass name. Now it's time to implement `forward:from:` for our `KANetworkHub`. As the hub has no routing information we're forced to rely on a flood routing algorithm. But... remember the constraint as outlined in our test above: we do not want our hub forwarding the packet back to the originating node. We suggest taking advantage of the message `linksTowards:do:` that performs an action for all given links to one address: ```language=smalltalk KANetworkHub >> forward: aPacket from: arrivalLink + ... Your code ... ``` Now we can use a proper hub in our test, replacing the relevant line in `buildNetwork`, and then check that the `testSendViaHub` unit test passes. ```language=smalltalk hub := KANetworkHub withAddress: #hub. ``` You have now a nice basis for simulating networks. But we can do more! In the following section we will present some possible extensions. ### Other examples of specialized nodes In this section we will present some extensions to our network simulation package that will allow us to support new scenarios. We will propose some tasks to make sure that the extensions are fully working. In this section we do not define tests - but we _strongly_ encourage you to start to write your own tests. At this point in the book you should be comfortable writing your own tests, and should see their value for improving your development process. You should also consider writing your tests _first_, before you start implementing the examples below, following the practice of Test Driven Development. So please take this opportunity to practice. #### Workstations counting received packets We would like to know how many packets specific nodes are receiving. In particular when a _workstation_ node consumes a packet, it increments a packet counter. % consider asking reader to write the test now? Something like... !!important Can you write a test for the requirement above? Do you think you could use it to drive the development of a `KANetworkWorkstation` class, whose instances implement the above behaviour? What other behaviours should the instances exhibit? If you can, compare your solution to the one below. If you don't feel comfortable writing your test first yet, don't worry; just continue reading and implement the solution below. Let's start by subclassing `KANetworkNode`: ```language=smalltalk KANetworkNode subclass: #KANetworkWorkstation + instanceVariableNames: 'receivedCount' + classVariableNames: '' + category: 'NetworkSimulator-Nodes' ``` % comment about the new package We need to initialize the `receivedCount` instance variable for workstations. Redefining `initialize` should do the trick but, because we inherit instance variables from the superclass \(`address`, `outgoingLinks`, `arrivedPackets` and `loopback`\), it's really important to send the `super initialize` message. If we didn't, some of the those instance variables would not receive their default values \(`address` is initialized in the `KANetworkNode >> withAddress:` constructor, which our workstations will also use\). ```language=smalltalk KANetworkWorkstation >> initialize + super initialize. + receivedCount := 0 ``` Now we can override `consume:` in `KANetworkWorkstation`: ```language=smalltalk KANetworkWorkstation >> consume: aPacket + receivedCount := receivedCount + 1 ``` We should also: - define accessors and the `printOn:` method for debugging. - define a test \(or multiple tests\) for the behavior of workstation nodes. #### Printers accumulating printouts When a printer consumes a packet, it prints it. We can model the output tray of a printer as a list where packet payloads get stored, and the supply tray as the number of blank sheets remaining in the printer. !!important Again, if you feel comfortable writing a test first based on these requirements, feel free to do so before reading any further and then compare your solution to the one below. The implementation is very similar to what we did for workstations: we subclass `KANetworkNode` and redefine the `consume:` method: ```language=smalltalk KANetworkNode subclass: #KANetworkPrinter + instanceVariableNames: 'supply output' + classVariableNames: '' + category: 'NetworkSimulator-Nodes' ``` ```language=smalltalk KANetworkPrinter >> consume: aPacket + supply > 0 ifFalse: [ ^ self "no paper, do nothing" ]. + + supply := supply - 1. + output add: aPacket payload ``` Initialization is a bit different, though; since the standard `initialize` method has no argument, the only sensible initial value for the `supply` instance variable is zero: ```language=smalltalk KANetworkPrinter >> initialize + super initialize. + supply := 0. + tray := OrderedCollection new ``` We therefore need a way to pass the initial supply of paper available to a fresh instance: ```language=smalltalk KANetworkPrinter >> resupply: paperSheets + supply := supply + paperSheets ``` For convenience, we can provide an extended constructor to create printers with a non-empty supply in one message: ```language=smalltalk KANetworkPrinter class >> withAddress: anAddress initialSupply: paperSheets + ^ (self withAddress: anAddress) + resupply: paperSheets; + yourself ``` To finish up we can: - define some useful accessors and our good friend the `printOn:` method for debugging purpose - define some test methods for the behavior of printer nodes \(if you haven't done so already\) - extend the behaviour further. Are there more messages that a printer could usefully respond to? #### Servers that answer a request Let's make a server which responds to a packet by taking its payload, converting it to uppercase, and then sends it back to the server which sent it. !!important Again, you can stop here and try to drive the implementation our new server with tests if you're comfortable doing that. Otherwise, read on. This is another subclass of `KANetworkNode` which redefines the `consume:` method, but this time the node is stateless, so we have no initialization or accessor methods to write: ```language=smalltalk KANetworkNode subclass: #KANetworkEchoServer + instanceVariableNames: '' + classVariableNames: '' + category: 'NetworkSimulator-Nodes' ``` ```language=smalltalk KANetworkEchoServer >> consume: aPacket + | response | + response := aPacket payload asUppercase. + self send: (KANetworkPacket + from: self address + to: aPacket sourceAddress + payload: response) ``` % I think we have a problem here. Please correct me if I'm wrong, but at this point there is no way for a ==KANetworkLink== to transmit a packet along to the destination of the link when we don't have a reference to the packet: ==transmit:== needs a packet argument. So I don't think this will work! % I've not got any good ideas for this. Some of my bad ideas are: % - expose the Set of packets in a link through an accessor so we can get a reference % - a new method to transmit all the packets in a link % - a specialisation on ==KANetworkLink== for a link which ==transmit:== whenever we call ==emit:== % Probably the first is best. % The other question is how to handle this diactically. Paragraph to explain the problem, with a solution? The problem, with hints towards the solution? Or just hints towards the problem? Define a test for the behavior of server nodes. ### Conclusion In this chapter we built a little network simulation system, step by step. We showed the benefit of good protocol decompositions. As a further extension, we suggest modeling a more realistic network with cycles, as shown in Figure *@routing@*. Making this work properly will require replacing our primitive hubs with real routers, and flood routing with more realistic routing algorithms. ![A possible extension: a more realistic network with a cycle between three router nodes. ](figures/lan-routes.pdf width=50&label=routing) To start you off, here is a possible setup for a new family of tests: ```language=smalltalk KARoutingNetworkTest >> buildNetwork + | routers | + net := KANetwork new. + + routers := #(A B C) collect: + [ :each | KANetworkHub withAddress: each ]. + net connect: routers first to: routers second. + net connect: routers second to: routers third. + net connect: routers third to: routers first. + + #(a1 a2) do: [ :addr | + net connect: routers first + to: (KANetworkNode withAddress: addr) ]. + #(b1 b2 b3) do: [ :addr | + net connect: routers second + to: (KANetworkNode withAddress: addr) ]. + net connect: routers third + to: (KANetworkNode withAddress: #c1) ``` \ No newline at end of file diff --git a/Chapters/SimpleLAN/SimpleLANSolution.md b/Chapters/SimpleLAN/SimpleLANSolution.md new file mode 100644 index 0000000..c44d976 --- /dev/null +++ b/Chapters/SimpleLAN/SimpleLANSolution.md @@ -0,0 +1,77 @@ +## Network simulator solutions ### Packets ```language=smalltalk KANetworkPacket class >> from: sourceAddress to: destinationAddress payload: anObject + ^ self new + initializeSource: sourceAddress + destination: destinationAddress + payload: anObject ``` ```language=smalltalk KANetworkPacket >> initializeSource: source destination: destination payload: anObject + sourceAddress := source. + destinationAddress := destination. + payload := anObject ``` ```language=smalltalk KANetworkPacket >> sourceAddress + ^ sourceAddress ``` ```language=smalltalk KANetworkPacket >> destinationAddress + ^ destinationAddress ``` ```language=smalltalk KANetworkPacket >> payload + ^ payload ``` ### Nodes ```language=smalltalk KANetworkNode >> initializeAddress: aNetworkAddress + address := aNetworkAddress ``` ```language=smalltalk KANetworkNode >> address + ^ address ``` ### Links ```language=smalltalk KANetworkLink >> initializeFrom: sourceNode to: destinationNode + source := sourceNode. + destination := destinationNode. ``` ```language=smalltalk KANetworkLink >> source + ^ source ``` ```language=smalltalk KANetworkLink >> destination + ^ destination ``` ```language=smalltalk Object subclass: #KANetworkNode + instanceVariableNames: 'address outgoingLinks' + classVariableNames: '' + category: 'NetworkSimulator-Core' ``` ```language=smalltalk KANetworkNode >> hasLinkTo: anotherNode + ^ outgoingLinks + anySatisfy: [ :any | any destination == anotherNode ] ``` ### Sending a packet ```language=smalltalk KANetworkLink >> isTransmitting: aPacket + ^ packetsToTransmit includes: aPacket ``` ### Transmitting a packet ```language=smalltalk KANetworkLink >> transmit: aPacket + "Transmit aPacket to the destination node of the receiver link." + (self isTransmitting: aPacket) + ifTrue: [ + packetsToTransmit remove: aPacket. + destination receive: aPacket from: self ] ``` ```language=smalltalk Object subclass: #KANetworkNode + instanceVariableNames: 'address outgoingLinks arrivedPackets' + classVariableNames: '' + category: 'NetworkSimulator-Core' ``` ```language=smalltalk KANetworkNode >> initialize + outgoingLinks := Set new. + arrivedPackets := OrderedCollection new ``` ```language=smalltalk KANetworkNode >> hasReceived: aPacket + ^ arrivedPackets includes: aPacket ``` ### The loopback link ```language=smalltalk KANetworkNode >> initialize + loopback := KANetworkLink from: self to: self. + outgoingLinks := Set new. + arrivedPackets := OrderedCollection new ``` ```language=smalltalk KANetworkNode >> linksTowards: anAddress do: aBlock + "Simple flood algorithm: route via all outgoing links. + However, just loopback if the receiver node is the routing destination." + anAddress = address + ifTrue: [ aBlock value: self loopback ] + ifFalse: [ outgoingLinks do: aBlock ] ``` ### Modeling the network itself ```language=smalltalk KANetworkTest >> buildNetwork + alone := KANetworkNode withAddress: #alone. + + net := KANetwork new. + + hub := KANetworkNode withAddress: #hub. + #(mac pc1 pc2 prn) do: [ :addr | + | node | + node := KANetworkNode withAddress: addr. + net connect: node to: hub ]. + + net + connect: (KANetworkNode withAddress: #ping) + to: (KANetworkNode withAddress: #pong) ``` ```language=smalltalk KANetwork >> initialize + nodes := Set new. + links := Set new ``` ```language=smalltalk KANetwork >> connect: aNode to: anotherNode + self add: aNode. + self add: anotherNode. + links add: (self makeLinkFrom: aNode to: anotherNode) attach. + links add: (self makeLinkFrom: anotherNode to: aNode) attach ``` ### Looking up nodes ```language=smalltalk KANetwork >> nodeAt: anAddress ifNone: noneBlock + ^ nodes + detect: [ :any | any address = anAddress ] + ifNone: noneBlock ``` ### Looking up links ```language=smalltalk KANetwork >> linkFrom: sourceAddress to: destinationAddress + ^ links + detect: [ :anyLink | + anyLink source address = sourceAddress + and: [ anyLink destination address = destinationAddress ] ] + ifNone: [ + NotFound + signalFor: sourceAddress -> destinationAddress + in: self ] ``` ### Packet delivery with forwarding ```language=smalltalk KANetworkHub >> forward: aPacket from: arrivalLink + self + linksTowards: aPacket destinationAddress + do: [ :link | + link destination == arrivalLink source + ifFalse: [ self send: aPacket via: link ] ] ``` \ No newline at end of file diff --git a/Chapters/SnakesAndLadders/SnakesAndLadders.md b/Chapters/SnakesAndLadders/SnakesAndLadders.md new file mode 100644 index 0000000..22acd9e --- /dev/null +++ b/Chapters/SnakesAndLadders/SnakesAndLadders.md @@ -0,0 +1,411 @@ +## Snakes and ladders @cha:snakes Snakes and Ladders is a simple game suitable for teaching children how to apply rules \([http://en.wikipedia.org/wiki/Snakes\_and\_ladders](http://en.wikipedia.org/wiki/Snakes_and_ladders)\). It is dull for adults because there is absolutely no strategy involved, but this makes it easy to implement! In this chapter you will implement SnakesAndLadders and we use it as a pretext to explore design questions. ![An example Snakes and Ladders board with two ladders and a snake.](figures/snakesAndLadders.png width=70&label=fig:snakes) ### Game rules Snakes and Ladders originated in India as part of a family of die games. The game was introduced in England as "Snakes and Ladders" \(see Figure *@fig:snakes@*\), then the basic concept was introduced in the United States as _Chutes and Ladders_. Here is a brief description of the rules: - **Players:** Snakes and Ladders is played by two to four players, each with her/his own token to move around the board. - **Moving Player**: a player rolls a die, then moves the designated number of tiles, between one and six. Once he lands on a tile, she/he has to perform any action designated by the tile. \(Since the rules are fuzzy we decided that we can have multiple players in the same tile\). - **Ladders:** If the tile a player lands on is at the bottom of a ladder, she/he should climb the ladder, which brings him to a tile higher on the board. - **Snakes:** If the tile a player lands on is a head snake, she/he must slide down the snake, landing on a tile closer to the beginning. - **Winning:** the winner is the player who gets to the last tile first, whether by landing on it from a roll, or by reaching it with a ladder. We decided that when the player does not move if he does not land directly on the last tile, it does not move. ### Game possible run The code snippet below is a possible way to program this game. We take as a board configuration the one depicted in Figure *@fig:snakes@*. It defines a board game composed of 12 tiles with two ladders and one snake. We add two players and then start the game. ``` | jill jack game | +game := SLGame new tileNumber: 12. +game + setLadderFrom: 2 to: 6; + setLadderFrom: 7 to: 9; + setSnakeFrom: 11 to: 5. +game + addPlayer: (SLPlayer new name: 'Jill'); + addPlayer: (SLPlayer new name: 'Jack'). +game play ``` Since we want to focus on the game logic, you will develop a textual version of the game and avoid any lengthy user interface descriptions. The following is an example game execution: Two players are on the first tile. The board contains two ladders, \[2->6\] and \[7->9\], and one snake \[5<-11\]. Jill rolls a die and throws a 3 and moves to the corresponding tile. Jack rolls a die and throws a 6 and moves to the corresponding tile and follow its effect, climbing the ladder at tile 7 up to tile 9. Jack and Jill continue to alternate taking turns until Jill ends up on the last tile. ``` [1][2->6][3][4][5][6][7->9][8][9][10][5<-11][12] +throws 3: [1][2->6][3][4][5][6][7->9][8][9][10][5<-11][12] +throws 6: [1][2->6][3][4][5][6][7->9][8][9][10][5<-11][12] +throws 5: [1][2->6][3][4][5][6][7->9][8][9][10][5<-11][12] +throws 1: [1][2->6][3][4][5][6][7->9][8][9][10][5<-11][12] +throws 3: [1][2->6][3][4][5][6][7->9][8][9][10][5<-11][12] ``` ### Potential objects and responsibilities Take a piece of paper, study the game rules and list any potential objects and their behavior. This is an important exercise to practice, training yourself to discover potential objects and classes. Techniques such as _Responsibility Driven Design_ exist to help programmers during this phase of object discovery. Responsibility Driven Design suggests analysing the documents describing a project, and turning the subjects of sentences into candidate objects and grouping verbs as the behavior of these objects. Any synonyms are identifed and used to reduce and gather together similar objects or behavior. Then later objects are grouped into classes. Some alternate approaches look for relationship patterns between objects such as part-whole, locations, entity-owner... This could be the topic of a full book. Here we follow another path: sketching scenarios. We describe several scenarios and from such scenario we identify key playing objects. - Scenario 1. The game is created with a number of tiles. The game must have an end and start tiles. Ladders and snakes should be declared. - Scenario 2. Players are declared. They start on the first tiles. - Scenario 3. When player rolls a die, he should move the number of tiles given by the die. - Scenario 4. After moving the first player a given number of tiles based on the result of die roll, this is the turn of the second player. - Scenario 5. When a player arrives to a ladder start, it should be moved to the ladder end. - Scenario 6. When a player should move further than the end tile, he does not move. - Scenario 7. When a player ends its course on the end tile, he wins and the game is finished. Such scenarios are interesting because they are a good basis for tests. #### Possible class candidates When reading the rules and the scenario, here is a list of possible classes that we could use. We will refine it later and remove double or overlapping concepts. - Game: keeps track of the game state, the players, and whose turn it is. - Board: keeps the tile configuration. - Player: keeps track of location on the board and moving over tiles. - Tile: keeps track of any player on it. - Snake: is a special tile which sends a player back to an earlier tile. - Ladder: is a special tile which sends a player ahead to a later tile. - First Tile: holds multiple players at the beginning of the game. - Last Tile: players must land exactly on this tile, or else they do not move. - Die: rolls and indicates the number of tiles that a player must move over. It is not clear if all the objects we identify by looking at the problem and its scenario should be really turned into real objects. Also sometimes it is useful to get more classes to capture behavior and state variations. We should look to have an exact mapping between concepts identified in the problem scenario or description and the implementation. From analysing this list we can draw some observations: - Game and Board are probably the same concept and we can merge them. - Die may be overkill. Having a full object just to produce a random number may not be worth, especially since we do not have a super fancy user interface showing the die rolling and other effect. - Tile, Snake, Ladder, Last and First Tile all look like tiles with some variations or specific actions. We suspect that we can reuse some logic by creating an inheritance hierarchy around the concept of Tile. #### About representation We can implement the same system using different implementation choices. For example we could have only one class implementing all the game logic and it would work. Some people may also argue that this is not a bad solution. Object-oriented design favors the distribution of the state of the system to different objects. It is often better to have objects with clear responsibilities. Why? Because you should consider that you will have to rethink, modify or extend your system. We should be able to understand and extend easily a system to be able to reply to new requirements. Not having a nice object-oriented decomposition for a simple game may not be a problem, as soon as you will start to model a more complex system not having a good decomposition will hurt you. Real life applications often have a lifetime up to 25 years. In addition, imagine that we are a game designer and we want to experiment with different variations and tiles with new properties such as one super special tile changing other tiles, adding snakes before the current player to slow other participants. ### About object-oriented design When designing a system, you will often have questions that cannot be blindly and automatically answered. Often there is no definite answer. This is what is difficult with object-oriented design and this is why practicing is important. What composes the state of an object? The state of object should characterize the object over its lifetime. For example the name of player identifies the player. Now it may happen that some objects just because they are instances of different classes do not need the same state but still offer the same set of messages. For example the tiles and the ladder/snake tiles have probably a similar API but snake and ladder should hold information of their target tile. We can also distinguish between the intrinsic state of an object \(e.g., name of player\) and the state we use to represent the collaborators of an object. The other important and difficult question is about the relationships between the objects. For example imagine that we model a tile as an object, should this object points to the players it contains. Similarly, should a tile knows its position or just the game should know the position of each tile. Should the game object keep the position of the players or just the player. The game should keep the players list since it should compute who is the next player. #### CRC cards Some designers use CRC \(for Class Responsibility Collaborators\) cards: the idea is to take the list of classes we identified above. For each of them, they write on a little card: the class name, its responsibility in one or two sentences and list its collaborators. Once this is done, they take a scenario and see how the objects can play such a scenario. Doing so they refine their design by adding more information \(collaborators\) to a class or merging two classes or splitting a class into multiple ones when they fill that a class has too many responsibilities. To improve such process, some designers consider implementation concerns or alternatives and may create objects to represent such variations. #### Some heuristics To help us taking decision, that are some heuristics: - One object should have one main responsibility. - Move behavior close to data. If a class defines the behavior of another object, there is a good chance that other clients of this object are doing the same and create duplicated and complex logic. If an object defines a clear behavior, clients just invoke it without duplicating it. - Prefer domain object over literal objects. As a general principle it is better to get a reference to a more general objects than a simple number. Because we can then invoke a larger set of behavior. #### Kind of data passed around Even if in Pharo, everything is an object, storing a mere integer object instead of a full tile can lead to different solutions. There is no perfect solution mainly consequences of choices and you should learn how to assess a situation to see which one has better characteristics for your problem. Here is a question illustrating the problem: Should a ladder know the tile it forwards the player to or is the index of a tile enough? When designing the ladder tile behavior, we should understand how we can access the target tile where the player should be moved to. If we just give the index of the target to a ladder, the tile has to be able to access the board containing the tiles else it will be impossible to access to the target tile of the ladder. The alternative, i.e., passing the tile looks nicer because it represents a natural relation and there is no need to ask the board. #### Agility to adapt In addition it is important not to get stressed, writing tests that represent parts or scenario we want to implement is a good way to make sure that we can adapt in case we discover that we missed a point. Now this game is interesting also from a test point of view because it may be difficult to test the parts in isolation \(i.e., without requiring to have a game object\). ### Let us get started You will follow an iterative process and test first approach. You will take scenario implement a test and define the corresponding classes. This game implementation raises an interesting question which is how do we test the game state without hardcoding too much implementation details in the tests themselves. Indeed tests that validate scenario only involving public messages and high-level interfaces are more likely to be stable over time and do not require modifications. Indeed if we check the exact class of certain objects you will have to change the implementation as well as the tests when modifying the implementation. In addition, since in Pharo the tests are normal clients of the objects they test, writing some tests may force us to define extra methods to access to private data. But enough talking! Let us start by defining a test class named `SLGameTest`. We will see in the course of development if we define other test classes. Our feeling is that the tiles and players are objects with limited responsibility and their responsibility is best illustrated \(and then tested\) when they interact with each other in the context of a given game. Therefore the class `SLGameTest` describes the place in which relevant scenario will occur. Define the class `SLGameTest`. ``` TestCase subclass: #SLGameTest + instanceVariableNames: '' + classVariableNames: '' + package: 'SnakesAndLadders' ``` One of the first scenario is that a game is composed of a certain number of tiles. We can write a test as follows but it does not have a lot of value. At the beginning of the development, this is normal to have limited tests because we do not have enough objects to interact with. ``` SLGameTest >> testCheckingSimpleGame + + | game | + game := SLGame new tileNumber: 12. + self assert: game tileNumber equals: 12 ``` Now we should make this test pass. Some strong advocates of TDD say that we should code the first simplest method that would make the test pass and go to the next one. Let us see what it would be \(of course this method will be changed later\). First you should define the class `SLGame`. ``` Object subclass: #SLGame + instanceVariableNames: 'tiles' + classVariableNames: '' + package: 'SnakesAndLadders' ``` Now you can define the methods `tileNumber:` and `tileNumber`. This is not really nice because we should get a collection of tiles and now we put a number. ``` SLGame >> tileNumber: aNumber + tiles := aNumber ``` ``` SLGame >> tileNumber + ^ tiles ``` These method definitions are enough to make our test pass. It means that our test was not really good because tiles should hold a collection containing the tiles and not just a number. We will address this point later. ### A first real test Since we would like to be able to check that our game is correct we can use its textual representation and test it as a way to check the game state. The following test should what we want. ``` SLGameTest >> testPrintingSimpleGame + + | game | + game := SLGame new tileNumber: 12. + self + assert: game printString + equals: '[1][2][3][4][5][6][7][8][9][10][11][12]' ``` What we would like is that the printing of the game asks the tiles to print themselves this way we will be able to take advantage that there will be different tiles in a modular way: i.e. we will not change the game to display the ladder and snake just have different tiles with different behavior. The first step is then to define a class named `SLTile` as follows: ``` Object subclass: #SLTile + instanceVariableNames: '' + classVariableNames: '' + package: 'SnakesAndLadders' ``` Now we would like to test the printing of a single tile. So let us define a test case named `SLTileTest`. This test case will test some basic behavior but it is nice to decompose our implementation process. We are trying to minimize the gap between one functionality and one test. ``` TestCase subclass: #SLTileTest + instanceVariableNames: '' + classVariableNames: '' + package: 'SnakesAndLadders-Test' ``` Now we can write a simple test to make sure that we can print a tile. ``` SLTileTest >> testPrinting + + | tile | + tile := SLTile new position: 6. + self assert: tile printString equals: '[6]' ``` Tile position could have been managed by the game itself. But it means that we would have to ask the game for the position of a given tile and while it would work, it does not feel good. In Object-Oriented Design, we should distribute responsibilities to objects and their state is their first responsibility. Since the position is an attribute of a tile, better define it there. This is where you see that the fact that the code is running is not a quality test for good Object-Oriented Design. In particular it means that we should add an accessor to set the position and to add an instance variable `position` to the class `SLile`. Execute the test. You should get a debugger and use it to create a method `position:` as well as the instance variable. Now we can define the `printOn:` method for tiles as follows. We add a `[` into the stream, then we asked the position to print itself in the stream by sending it the message `printOn:` and we add `]` in the stream. Since the position is a simple integer, the result of the `position printOn: aStream` expression is just to add a string representing the number in the stream. ``` SLTile >> printOn: aStream + + aStream << '['. + position printOn: aStream. + aStream << ']' ``` Your tile test should pass now. When we read the definition of the method `printOn:` above we see that it also sends the message `printOn:` here to the number used for the position. Indeed, we can send messages with the same name to different objects and each object may react differently to these messages. We can also send a message with the same name than the method to the receiver to perform a recursive call, but as with any recursive call we should have a non recursive branch. We are ready to finish the printing of the game itself. Now we can define the method `printOn:` of the game to print all its tiles. Note that this will not work since so far we did not create tiles. ``` SLGame >> printOn: aStream + + tiles do: [ :aTile | + aTile printOn: aStream ] ``` We modify the method `tileNumber:` to create an array of the given size and store it inside the `tiles` instance variable and to put a new tile for each position. Pay attention the tile should have the correct position. ``` SLGame >> tileNumber: aNumber + ... Your code ... ``` Now your printing tests should be working both for the tile and the game. But wait if we run the test `testCheckingSimpleGame` it fails. Indeed we did not change the definition `tileNumber`. Do it and make sure that your tests all pass. And save your code. ### Accessing one tile Now we will need to be able to ask the game for a given tile, for example with the message `tileAt:`. Let us add a test for it. ``` SLGameTest >> testTileAt + + | game | + game := SLGame new tileNumber: 12. + self assert: (game tileAt: 6) printString equals: '[6]' ``` Define the method `tileAt:`. ``` SLGame >> tileAt: aNumber + ... Your code ... ``` ### Adding players Now we should add players. The first scenario to test is that when we add a player to game, it should be on the first tile. Let us write a test: we create a game and a player. Then we add the player to the game and the player should be part of the players of the first tile. ``` SLGameTest >> testPlayerAtStart + + | game jill | + game := SLGame new tileNumber: 12. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + self assert: ((game tileAt: 1) players includes: jill). ``` ``` Object subclass: #SLPlayer + instanceVariableNames: 'name' + classVariableNames: '' + package: 'SnakesAndLadders' ``` Define the method `name:` in the class `SLPlayer`. Now we should think a bit how we should manage the players. We suspect that the game itself should get a list of players so that in the future it can ask each player to play its turn. Notice the previous sentence: we say each player to play and not the game to play the next turn - again this is Object-Oriented Design in action. Now our test does not really cover the point that the game should keep track of the players so we will not do it. Similarly we may wonder if a player should know its position. At this point we do not know and we postpone this decision for another scenario. ``` SLGame >> addPlayer: aPlayer + (tiles at: 1) addPlayer: aPlayer ``` Now what is clear is that a tile should keep a player list. Add an instance variable `players` to the `SLTile` class and initialize it to be an OrderedCollection. ``` SLTile >> initialize + ... Your code ... ``` Then implement the method `addPlayer:` ``` SLTile >> addPlayer: aPlayer + ... Your code ... ``` Now all your tests should pass. Let us the opportunity to write better tests. We should check that we can add two players and that both are on the starting tile. ``` SLGameTest >> testSeveralPlayersAtStart + + | game jill jack | + game := SLGame new tileNumber: 12. + jill := SLPlayer new name: 'Jill'. + jack := SLPlayer new name: 'Jack'. + game addPlayer: jill. + game addPlayer: jack. + self assert: ((game tileAt: 1) players includes: jill). + self assert: ((game tileAt: 1) players includes: jack). ``` All the tests should pass. This is the time to save and take a break. ![Playground in action. Use Do it and go - to get an embedded inspector.](figures/playground.png width=70&label=fig:snakesplay) ### Avoid leaking implementation information We are not really happy with the previous tests for example `testPlayerAtStart`. ``` SLGameTest >> testPlayerAtStart + + | game jill | + game := SLGame new tileNumber: 12. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + self assert: ((game tileAt: 1) players includes: jill). ``` Indeed a test is a first client of our code. Here we see in the expression `players includes: jill` that we have to know that players are held in a collection and that this collection includes such a player. It can be a real problem if later we decide to change how we manage players, since we will have to change all the places using the result of the `players` message. Let us address this issue: define a method `includesPlayer:` that returns whether a tile has the given player. ``` SLTile >> includesPlayer: aPlayer + ... Your code ... ``` Now we can rewrite the two tests `testPlayerAtStart` and `testSeveralPlayersAtStart` to use this new message. ``` SLGameTest >> testPlayerAtStart + + | game jill | + game := SLGame new tileNumber: 12. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + self assert: ((game tileAt: 1) includesPlayer: jill). ``` ``` SLGameTest >> testSeveralPlayersAtStart + + | game jill jack | + game := SLGame new tileNumber: 12. + jill := SLPlayer new name: 'Jill'. + jack := SLPlayer new name: 'Jack'. + game addPlayer: jill. + game addPlayer: jack. + self assert: ((game tileAt: 1) includesPlayer: jill). + self assert: ((game tileAt: 1) includesPlayer: jack). ``` ### About tools Pharo is a living environment in which we can interact with the objects. Let us see a bit of that in action now. Type the following game creation in a playground \(as shown in Figure *@fig:snakesplay@*\). ``` | game jill jack | +game := SLGame new tileNumber: 12. +jill := SLPlayer new name: 'Jill'. +jack := SLPlayer new name: 'Jack'. +game addPlayer: jill. +game addPlayer: jack. +game ``` Now you can inspect the game either using the inspect command-i or sending the message `inspect` to the game as in `game inspect`. You can also use the _do it and go_ menu item of a playground window. You should get a picture similar to the one *@fig:ginspector1@*. We see that the object is a `SLGame` instance and it has an instance variable named `tiles`. You can navigate on the instance variables as shown in Figure *@fig:ginspector2@*. Figure *@fig:ginspector3@* shows that we can navigate the object structure: here we start from the game, go to the first tile and see the two players. At any moment you can interact with the selected object sending it messages. ![Inspecting the game: a game instance and its instance variable `tiles`.](figures/inspector1.png width=70&label=fig:ginspector1) ![Navigating inside the game: getting inside the tiles and checking the players.](figures/inspector2.png width=70&label=fig:ginspector2) ![Navigating the objects using the navigation facilities of the inspector.](figures/inspector3.png width=100&label=fig:ginspector3) ### Displaying players Navigating the structure of the game is nice when we want to debug and interact with the game entities. Now we propose to display the player objects in a nicer way. We will reuse such behavior when printing the game to follow the movement of the player on the board. Since we love testing, let us write a test describing what we expect when displaying a game. ``` SLGameTest >> testPrintingSimpleGameWithPlayers + + | game jill jack | + game := SLGame new tileNumber: 12. + jack := SLPlayer new name: 'Jack'. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. "first player" + game addPlayer: jack. + self + assert: game printString + equals: '[1][2][3][4][5][6][7][8][9][10][11][12]' ``` To make this test pass, you must define a `printOn:` on `SLPlayer`. Make sure that the `printOn:` of `SLTile` also invokes this new method. ``` SLPlayer >> printOn: aStream + ... Your code ... ``` Here is a possible implementation for the tile logic. ``` SLTile >> printOn: aStream + aStream << '['. + position printOn: aStream. + players do: [ :aPlayer | aPlayer printOn: aStream ]. + aStream << ']' ``` Run your tests, they should pass. ### Preparing to move players To move the player we need to know the tile on which it will arrive. We want to ask the game: what is the target tile if this player \(for example, jill\) is moving a given distance. Let us write a test for the message `tileFor: aPlayer atDistance: aNumber`. ``` SLGameTest >> testTileForAtDistance + + | jill game | + game := SLGame new tileNumber: 12. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + self assert: (game tileFor: jill atDistance: 4) position equals: 5. ``` What is implied is that a player should know its location or that the game should start to look from the beginning to find what is the current position of a player. The first option looks more reasonable in terms of efficiency and this is the one we will implement. Let us write a simpler test for the introduction of the position in a player. ``` SLGameTest >> testPlayerAtStartIsAtPosition1 + + | game jill | + game := SLGame new tileNumber: 12. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + self assert: jill position equals: 1. ``` Define the methods `position` and `position:` in the class `SLPlayer` and add an instance variable `position` to the class. If you run the test it should fail saying that it got nil instead of one. This is normal because we never set the position of a player. Modify the `addPlayer:` to handle this case. ``` SLGame >> addPlayer: aPlayer + ... Your code ... ``` The test `testPlayerAtStartIsAtPosition1` should now pass and we can return to the `testTileForAtDistance`. Since we lost a bit track, the best thing to do is to run our tests and check why they are failing. We get an error saying that a game instance does not understand the message `tileFor:atDistance:` this is normal since we never implemented it. For now we do not consider that a roll can bring the player further than the last tile. Let us fix that now. Define the method `tileFor:atDistance:` ``` SLGame >> tileFor: aPlayer atDistance: aNumber + ... Your code ... ``` Now all your test should pass and this is a good time to save your code. ### Finding the tile of a player We can start to move a player from a tile to another one. We should get the tile destination using the message `tileFor:atDistance:` and add the player there. Of course we should not forget that the tile where the player is currently positioned should be updated. So we need to know what is the tile of the player. Now once a player has position it is then easy to find the tile on top of which it is. Let us write a test for it. ``` SLGameTest >> testTileOfPlayer + + | jill game | + game := SLGame new tileNumber: 12. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + self assert: (game tileOfPlayer: jill) position equals: 1. ``` Implement the method `tileOfPlayer:`. ``` SLGame >> tileOfPlayer: aSLPlayer + ... Your code ... ``` ### Moving to another tile Now we are ready to work on moving a player from one tile to the other. Let us express a test: we create only one player. We test that after the move, the new position is the one of the target tile, that the original tile does not have player and the target tile has effectively the player. ``` SLGameTest >> testMovePlayerADistance + + | jill game | + game := SLGame new tileNumber: 12. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + game movePlayer: jill distance: 4. + self assert: jill position equals: 5. + self assert: (game tileAt: 1) players isEmpty. + self assert: ((game tileAt: 5) includesPlayer: jill). ``` What is hidden in this test is that we should be able to remove a player from a tile. Since we should remove the player of a tile when it moves, implement the method ``` SLTile >> removePlayer: aPlayer + ... Your code ... ``` Now propose an implementation of the method `movePlayer: aPlayer distance: anInteger`. You should get the destination tile for the player, remove the player from its current tile, add it to the destination tile and change the position of the player to reflect its new position. ``` SLGame >> movePlayer: aPlayer distance: anInteger + ... Your code ... ``` We suspect that when we will introduce ladder and snake tiles, we will have to revisit this method because snakes and ladders do not store players just move them around. #### About our implementation The implementation that we propose below for the method `movePlayer: aPlayer distance: anInteger` is not as nice as we would like it to be. Why? Because it does not give a chance to the tiles to extend this behavior and our experience tells us that we will need it when we will introduce the snake and ladder. We will discuss that when we will arrive there. ``` SLGame >> movePlayer: aPlayer distance: anInteger + | targetTile | + targetTile := self tileFor: aPlayer atDistance: anInteger. + (self tileOfPlayer: aPlayer) removePlayer: aPlayer. + targetTile addPlayer: aPlayer. + aPlayer position: targetTile position. ``` ![Current simple design: three classes with a player acting a simple object.](figures/SLDesign0.pdf width=70&label=fig:sldesign0) ### Snakes and ladders Now we can introduce the two special tiles: the snakes and ladders. Let us analyse a bit their behavior: when a player lands on such a tile, it is automatically moved to another tile. As such, snake and ladder tiles do not need to keep references to players because players never stay on them. Snakes is really similar to ladders: we could just have a special kind of tiles to manage them. Now we will define two separate classes so that we can add extra behavior. Remember creating a class is cheap. One behavior we will implement is a different printed version so that we can identify the kind of tile we have. At the beginning of the chapter we used `->` for ladders and `<-` for snakes. ``` [1][2->6][3][4][5][6][7->9][8][9][10][5<-11][12] ``` ### A hierarchy of tiles We have now our default tile and two kinds of different _active_ tiles. Now we will split our current tile class to be able to reuse a bit of its state and behavior with the new tiles. Our current tile class will then be one of the leaves of our hierarchy tree. To factor the behavior of the active tiles, we will introduce a new class named `ActiveTile`. Once we will be done, we should have a hierarchy as the one presented in the Figure *@fig:sldesign1@*. ![A hierarchy of tiles.](figures/SLDesign1.pdf width=70&label=fig:sldesign1) Let us start create the hierarchy. #### Split Tile class in two Let us do the following actions: - Using the class refactoring "insert superclass" \(click on the `SLTile` and check the class refactoring menu\), introduce a new superclass to `SLTile`. Name it `SLAbstractTile`. - Run the tests and they should pass. - Using the class instance variable refactoring "pull up", push the position instance variable - Run the tests and they should pass. - Using the method refactoring "push up", push the methods `position` and `position:`. - Run the tests and they should pass. What you see is that we did not execute the actions randomly but we want to control that each step is under control using the tests. Here are the classes and methods `printOn:`. ``` Object subclass: #SLAbstractTile + instanceVariableNames: 'position' + classVariableNames: '' + package: 'SnakesAndLadders' ``` Define a `printOn:` method so that all the subclasses can be displayed in the board by their position. ``` SLAbstractTile >> printOn: aStream + aStream << '['. + position printOn: aStream. + aStream << ']' ``` ``` SLAbstractTile subclass: #SLTile + instanceVariableNames: 'players' + classVariableNames: '' + package: 'SnakesAndLadders' ``` #### Adding snake and ladder tiles Now we can add a new subclass to `SLAbstractTile`. ``` SLAbstractTile subclass: #SLActiveTile + instanceVariableNames: 'targetTile' + classVariableNames: '' + package: 'SnakesAndLadders' ``` We add a method `to:` to set the destination tile. ``` SLActiveTile >> to: aTile + targetTile := aTile ``` Then we add the two new subclasses of `SLActiveTile` ``` SLActiveTile subclass: #SLSnakeTile + instanceVariableNames: '' + classVariableNames: '' + package: 'SnakesAndLadders' ``` ``` SLSnakeTile >> printOn: aStream + + aStream << '['. + targetTile position printOn: aStream. + aStream << '<-'. + position printOn: aStream. + aStream << ']' ``` ``` SLActiveTile subclass: #SLLadderTile + instanceVariableNames: '' + classVariableNames: '' + package: 'SnakesAndLadders' ``` This is fun to see that the order when to print the position of the tile is different between the snakes and ladders. ``` SLLadderTile >> printOn: aStream + + aStream << '['. + position printOn: aStream. + aStream << '->'. + targetTile position printOn: aStream. + aStream << ']' ``` We did on purpose not to ask you to define tests to cover the changes. This exercise should show you how long sequence of programming without adding new tests expose us to potential bugs. They are often more stressful. So let us add some tests to make sure that our code is correct. ``` SLTileTest >> testPrintingLadder + + | tile | + tile := SLLadderTile new position: 2; to: (SLTile new position: 6). + self assert: tile printString equals: '[2->6]' ``` ``` SLTileTest >> testPrintingSnake + + | tile | + tile := SLSnakeTile new position: 11; to: (SLTile new position: 5). + self assert: tile printString equals: '[5<-11]' ``` Run the tests and they should pass. Save your code. Take a rest! ### New printing hook When we look at the printing situation we see code duplication logic. For example, we always see at least the repetition of the first and last expression. ``` SLTile >> printOn: aStream + + aStream << '['. + position printOn: aStream. + players do: [ :aPlayer | aPlayer printOn: aStream ]. + aStream << ']' ``` ``` SLLadderTile >> printOn: aStream + + aStream << '['. + position printOn: aStream. + aStream << '->'. + targetTile position printOn: aStream. + aStream << ']' ``` Do you think that we can do better? What would be the solution? In fact what we would like is to have a method that we can reuse and that handles the output of `'[ ]'`. And in addition we would like to have another method for the contents between the parentheses and that we can specialize it. This way each class can define its own behavior for the inside part and reuse the parenthesis part. ![Introducing `printInsideOn:` as a new hook.](figures/SLDesign2.pdf width=70&label=fig:sldesign2) This is what you will do now. Let us split the `printOn:` method of the class `SLAbstractTile` in two methods: - a new method named `printInsideOn:` just printing the position, and - the `printOn:` method using this new method. ``` SLAbstractTile >> printInsideOn: aStream + + position printOn: aStream ``` Now define the method `printOn:` to produce the same behavior as before but calling the message `printInsideOn:`. ``` SLAbstractTile >> printOn: aStream + ... Your code ... ``` Run your tests and they should pass. You may have noticed that this is normal because none of them is covering the abstract tile. We should have been more picky on our tests. What you should see is that we will have only one method defining the behavior of representing the surrounding of a tile and this is much better if one day we want to change it. ### Using the new hook Now you are ready to express the printing behavior of `SLTile`, `SLSnake` and `SLLadder` in a much more compact fashion. Do not forget to remove the `printOn:` methods in such classes, else they will hide the new behavior \(If you do not get why you should read again the chapter on inheritance\). You should get the situation depicted as in Figure *@fig:sldesign2@*. Here is our definition for the `printInsideOn:` method of the class `SLTile`. ``` SLTile >> printInsideOn: aStream + + super printInsideOn: aStream. + players do: [ :aPlayer | aPlayer printOn: aStream ]. ``` What you should see is that we are invoking the default behavior \(from the class `SLAbstractTile`\) using the `super` pseudo-variable and we enrich it with the information of the players. Define the one for the `SLLadderTile` class and the one for `SLSnakeTile`. ``` SLLadderTile >> printInsideOn: aStream + ... Your code ... ``` ``` SLSnakeTile >> printInsideOn: aStream + ... Your code ... ``` #### super does not have to be the first expression Now we show you our definition of `printInsideOn:` for the class `SLSnakeTile`. Why do we show it? Because it shows you that an expression invoking an overriden method can be placed anywhere. It does not have to be the first expression of a method. Here it is the last one. ``` SLSnakeTile >> printInsideOn: aStream + + targetTile position printOn: aStream. + aStream << '<-'. + super printInsideOn: aStream ``` Do not forget to run your tests. And they should all pass. ### About hooks and templates If we look at what we did. We created what is called a Hook/Template. - The template method is the `printOn:` method. It defines a context of the execution of the hook methods. - The `printInsideOn:` message is the hook that get specialized for each subclass. It happens in the context of a template method. What you should see is that the `printOn:` message is also a hook of the `printString` message. There the `printString` method is creating a context and send the message `printOn:` which gets specialized. The second point that we want to stress is that we turned expressions into a self-message. We transformed the expressions `position printOn: aStream` into `self printInsideOn: aStream` and such simple transformation created a point of variation extensible using inheritance. Note that the expression could have been a lot more complex. Finally what is important to realize is that even `position printOn: aStream` creates a variation point. Imagine that we have multiple kind of positions, this expression will invoke the corresponding method on the object that is currently referred to by `position`. Such position objects could or not be organized in a hierarchy as soon as they offer a similar interface. So each message is in fact a variation point in a program. ### Snake and ladder declaration Now we should add to the game some messages to declare snake and ladder tiles. We propose to name the messages `setLadderFrom:to:` and `setSnakeFrom:to:`. Now let us write a test and make sure that it fails before starting. ``` SLGameTest >> testFullGamePrintString + + | game | + game := SLGame new tileNumber: 12. + game + setLadderFrom: 2 to: 6; + setLadderFrom: 7 to: 9; + setSnakeFrom: 11 to: 5. + self + assert: game printString + equals: '[1][2->6][3][4][5][6][7->9][8][9][10][5<-11][12]' ``` Define the method `setSnakerFrom:to:` that takes two positions, the first one is the position of the tile and the second one is the position of the target. Pay attention that the message `to:` of the active tiles expects a tile and not a position. ``` SLGame >> setSnakeFrom: aSourcePosition to: aTargetPosition + ... Your code ... ``` ``` SLGame >> setLadderFrom: aSourcePosition to: aTargetPosition + ... Your code ... ``` Run your tests! And save your code. ### Better tile protocol Now we should define what should happen when a player lands on an active tiles \(snake or ladder\). Indeed for the normal tiles, we implemented that the player change its position, then the origin tile loses the player and the receiving tile gains the player. We implemented such behavior in the method `movePlayer: aPlayer distance: anInteger` shown below. We paid attention that a player cannot be in two places at the same time: we remove it from its tile, then move it to its destination. ``` SLGame >> movePlayer: aPlayer distance: anInteger + | targetTile | + targetTile := self tileFor: aPlayer atDistance: anInteger. + (self tileOfPlayer: aPlayer) removePlayer: aPlayer. + targetTile addPlayer: aPlayer. + aPlayer position: targetTile position. ``` At that moment we said that we did not like too much this implementation. And now this is the time to understand why and do improve the situation. First it would be good that the behavior to manage the entering and leaving of a tile would be closer to the objects performing it. We have two solutions: we could move it to the tile or to the player class. Second, we should take another factor into play: different tiles have different behaviors; normal tiles manage players, but active tiles do not, instead they place players on their target tile. Therefore it is more interesting to define a variation point on the tile, because we will be able to exploit it for normal and active tiles. We propose to define two methods on the tile: one to accept a new player named `acceptPlayer:` and to release a player named `releasePlayer:`. Let us rewrite `movePlayer: aPlayer distance: anInteger` with such methods. ``` SLTile >> acceptPlayer: aPlayer + self addPlayer: aPlayer. + aPlayer position: position. ``` The use in this definition of self messages or direct instance variable access is an indication that definition belongs to this class. Now we define the method `releasePlayer:` as follows: ``` SLTile >> releasePlayer: aPlayer + self removePlayer: aPlayer ``` Defining the method `releasePlayer:` was not necessary but we did it because it is more symmetrical. Now we can redefine `movePlayer: aPlayer distance: anInteger`. ``` SLGame >> movePlayer: aPlayer distance: anInteger + | targetTile | + targetTile := self tileFor: aPlayer atDistance: anInteger. + (self tileOfPlayer: aPlayer) releasePlayer: aPlayer. + targetTile acceptPlayer: aPlayer. ``` All the tests should pass. And this is the power of test driven development, we change the implementation of our game and we can verify that we did not change its behavior. ![acceptPlayer: and releasePlayer: new message.](figures/SLDesign3.pdf width=70&label=fig:sldesign3) #### Another little improvement Now we can improve the definition of `acceptPlayer:`. We can implement its behavior partly on `SLAbstractTile` and partly on `SLTile`. This way the definition of the methods are closer to the definition of the instance variables and the state of the objects. ``` SLAbstractTile >> acceptPlayer: aPlayer + aPlayer position: position ``` ``` SLTile >> acceptPlayer: aPlayer + super acceptPlayer: aPlayer. + self addPlayer: aPlayer ``` Note that we change the order of execution by invoking the superclass behavior first \(using `super acceptPlayer: aPlayer`\) because we prefer to invoke first the superclass method, because we prefer to think that a subclass is extending an existing behavior. To be complete, we define that `releasePlayer:` does nothing on `SLAbstractTile`. We define it to document the two faces of the protocol. Figure *@fig:sldesign3@* shows the situation. ``` SLAbstractTile >> releasePlayer: aPlayer + "Do nothing by default, subclasses may modify this behavior." ``` ### Active tile actions Now we are ready to implement the behavior of the active tiles. But.... yes we will write a test first. What we want to test is that when a player lands on a snake it falls back on the target and that the original tile does not have this player anymore. This is what this test expresses. ``` SLGameTest >> testPlayerStepOnASnake + + | jill game | + game := SLGame new + tileNumber: 12; + setLadderFrom: 2 to: 6; + setLadderFrom: 7 to: 9; + setSnakeFrom: 11 to: 5. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + game movePlayer: jill distance: 10. + self assert: jill position equals: 5. + self assert: (game tileAt: 1) players isEmpty. + self assert: ((game tileAt: 5) includesPlayer: jill). ``` Now we just have to implement it! ``` SLActiveTile >> acceptPlayer: aPlayer + ... Your code ... ``` There is nothing to do for the message `releasePlayer:`, because the player is never added to the active tile. Once you are done run the tests and save. ### Alternating players We are nearly finished with the game. First we should manage that each turn a different player is playing and that the game finishes when the current player lands on the final tile. We would like to be able to: - make the game play in automatic mode - make the game one step at the time so that humans can play. The logic for the automatic play can be expressed as follows: ``` play + [ self isNotOver ] whileTrue: [ + self playPlayer: (self currentPlayer) roll: 6 atRandom ] ``` Until the game is finished, the game identifies the current player and plays this player for a given number given by a die of six faces. The expression `6 atRandom` selects randomly a number between 1 and 6. ### Player turns and current player The game does not keep track of the players and their order. We will have to support it so that each player can play in alternance. It will also help us to compute the end of the game. Given a turn, we should identify the current player. The following test verifies that we obtain the correct player for a given turn. ``` SLGameTest >> testCurrentPlayer + + | jack game jill | + game := SLGame new tileNumber: 12. + jack := SLPlayer new name: 'Jack'. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jack; addPlayer: jill. + game turn: 1. + self assert: game currentPlayer equals: jack. + game turn: 2. + self assert: game currentPlayer equals: jill. + game turn: 3. + self assert: game currentPlayer equals: jack. ``` You should add two instance variables `players` and `turn` to the `SLGame` class. Then you should initialize the two new instance variables adequately: the `players` instance variable to an OrderedCollection and the `turn` instance variable to zero. ``` SLGame >> initialize + ... Your code ... ``` You should modify the method `addPlayer:` to add the player to the list of players as shown by the method below. ``` SLGame >> addPlayer: aPlayer + aPlayer position: 1. + players add: aPlayer. + (tiles at: 1) addPlayer: aPlayer ``` We define also the setter method `turn:` to help us for the test. This is where you see that it would be good in Pharo to have the possibility to write tests inside the class and not to be forced to add a method definition just for a test but SUnit does not allow such behavior. One approach to resolve this, and ensuring only test code makes use of `turn:`, is to use class extensions. We make `turn:` belong to the `*SnakesAndLadders-Test` protocol. In this way if we only load the `SnakesAndLadders` package then it will not include any test specific methods. ``` SLGame >> turn: aNumber + turn := aNumber ``` ### How to find the logic of currentPlayer? Now we should define the method `currentPlayer`. We will try to show you how we brainstorm and experiment when we are looking for an algorithm or even the logic of a simple method. Imagine a moment that we have two players Jack-Jill. The turns are the following ones: Jack 1, Jill 2, Jack 3, Jill 4, Jack 5..... Now we know that we have two players. So using this information, at turn 5, the rest of the division of 5 by 2, gives us 1 so this is the turn of the first player. At turn 4, the rest of the division of 5 by 2 is zero so we take the latest player: Jill. Here is an expression that shows the result when we have two players and we use the division. ``` (1 to: 10) collect: [ :each | each -> (each \\ 2) ] +> {1->1. 2->0. 3->1. 4->0. 5->1. 6->0. 7->1. 8->0. 9->1. 10->0} ``` Here is an expression that shows the result when we have three players and we use the division. ``` (1 to: 10) collect: [ :each | each -> (each \\ 3) ] +> {1->1. 2->2. 3->0. 4->1. 5->2. 6->0. 7->1. 8->2. 9->0. 10->1} ``` What you see is that each time we get 0, it means that this is the last player \(second in the first case and third in the second\). This is what we do with the following method. We compute the rest of the division. We obtain a number between 0 and the player number minus one. This number indicates the index of the number in the `players` ordered collection. When it is zero it means that we should take the latest player. ``` SLGame >> currentPlayer + + | rest playerIndex | + rest := (turn \\ players size). + playerIndex := (rest isZero + ifTrue: [ players size ] + ifFalse: [ rest ]). + ^ players at: playerIndex ``` Run your tests and make sure that they all pass and save. ### Game end Checking for the end of the game can be implemented in at least two ways: - the game can check if any of the player is on the last tile. - or when a player lands on the last tile, its effect is to end the game. We will implement the first solution but let us write a test first. ``` SLGameTest >> testIsOver + + | jack game | + game := SLGame new tileNumber: 12. + jack := SLPlayer new name: 'Jack'. + game addPlayer: jack. + self assert: jack position equals: 1. + game movePlayer: jack distance: 11. + self assert: jack position equals: 12. + self assert: game isOver. ``` Now define the method `isOver`. You can use the `anySatisfy:` message which returns true if one of the elements of a collection \(the receiver\) satisfies a condition. The condition is that a player's position is the number of tiles \(since the last tile position is equal to the number of tiles\). ``` SLGame >> isOver + ... Your code ... ``` #### Alternate solution To implement the second version, we can introduce a new tile `SLEndTile`. Here is the list of what should be done: - define a new class. - redefine the `acceptPlayer:` to stop the game. Note that it means that the tile should have a reference to the game. This should be added to this special tile. - initialize the last tile of the game to be an instance of such a class. ### Playing one move Before automating the play of the game we should make sure that a die roll will not bring our player outside the board. Here is a simple test covering the situations. ``` SLGameTest >> testCanMoveToPosition + + | game | + game := SLGame new tileNumber: 12. + self assert: (game canMoveToPosition: 8). + self assert: (game canMoveToPosition: 12). + self deny: (game canMoveToPosition: 13). ``` Define the method `canMoveToPosition:`. It takes as input the position of the potential move. ``` SLGame >> canMoveToPosition: aNumber + ... Your code ... ``` #### Playing one game step Now we are finally ready to finish the implementation of the game. Here are two tests that check that the game can play a step correctly, i.e., picking the correct player and moving it in the correct place. ``` SLGameTest >> testPlayOneStep + + | jill jack game | + game := SLGame new tileNumber: 12. + jack := SLPlayer new name: 'Jack'. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + game addPlayer: jack. + self assert: jill position equals: 1. + game playOneStepWithRoll: 3. + self assert: jill position equals: 4. + self assert: (game tileAt: 1) players size equals: 1. + self assert: ((game tileAt: 4) includesPlayer: jill) ``` ``` SLGameTest >> testPlayTwoSteps + + | jill jack game | + game := SLGame new tileNumber: 12. + jack := SLPlayer new name: 'Jack'. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + game addPlayer: jack. + game playOneStepWithRoll: 3. + game playOneStepWithRoll: 2. + "nothing changes for jill" + self assert: jill position equals: 4. + self assert: ((game tileAt: 4) includesPlayer: jill). + "now let us verify that jack moved correctly to tile 3" + self assert: (game tileAt: 1) players size equals: 0. + self assert: jack position equals: 3. + self assert: ((game tileAt: 3) includesPlayer: jack) ``` Here is a possible implementation of the method `playOneStepWithRoll:`. ``` SLGame >> playOneStepWithRoll: aNumber + + | currentPlayer | + turn := turn + 1. + currentPlayer := self currentPlayer. + Transcript show: currentPlayer printString, 'drew ', aNumber printString, ': '. + (self canMoveToPosition: currentPlayer position + aNumber) + ifTrue: [ self movePlayer: currentPlayer distance: aNumber ]. + Transcript show: self; cr. ``` ![Playing step by step inside the inspector.](figures/playingInsideInspector.png width=100&label=fig:playingInsideInspector) Now we can verify that when a player lands on a ladder it is getting up. ``` SLGameTest >> testPlayOneStepOnALadder + + | jill jack game | + game := SLGame new + tileNumber: 12; + setLadderFrom: 2 to: 6; + setLadderFrom: 7 to: 9; + setSnakeFrom: 11 to: 5. + jack := SLPlayer new name: 'Jack'. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + game addPlayer: jack. + game playOneStepWithRoll: 1. + self assert: jill position equals: 6. + self assert: (game tileAt: 1) players size equals: 1. + self assert: ((game tileAt: 6) includesPlayer: jill). ``` You can try this method inside an inspector and see the result of the moves displayed in the transcript as shown in Figure *@fig:playingInsideInspector@*. ``` | jill jack game | +game := SLGame new + tileNumber: 12; + setLadderFrom: 2 to: 6; + setLadderFrom: 7 to: 9; + setSnakeFrom: 11 to: 5. +jack := SLPlayer new name: 'Jack'. +jill := SLPlayer new name: 'Jill'. +game addPlayer: jill. +game addPlayer: jack. +game inspect ``` ![Automated play.](figures/AutomatedPlay.png width=100&label=fig:AutomatedPlay) ### Automated play Now we can can define the `play` method as follows and use it as shown in Figure *@fig:AutomatedPlay@*. ``` SLGame >> play + + Transcript show: self; cr. + [ self isOver not ] whileTrue: [ + self playOneStepWithRoll: 6 atRandom ] ``` #### Some final tests We would like to make sure that the player is not moved when it does not land on the last tile or that the game is finished when one player lands on the last tile. Here are two tests covering such behavior. ``` SLGameTest >> testPlayOneStepOnExactFinish + + | jill jack game | + game := SLGame new + tileNumber: 12; + setLadderFrom: 2 to: 6; + setLadderFrom: 7 to: 9; + setSnakeFrom: 11 to: 5. + jack := SLPlayer new name: 'Jack'. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + game addPlayer: jack. + + game playOneStepWithRoll: 11. + "jill lands on the finish tile!" + self assert: jill position equals: 12. + self assert: (game tileAt: 1) players size equals: 1. + self assert: ((game tileAt: 12) includesPlayer: jill). + self assert: game isOver. ``` ``` SLGameTest >> testPlayOneStepOnInexactFinish + + | jill jack game | + game := SLGame new + tileNumber: 12; + setLadderFrom: 2 to: 6; + setLadderFrom: 7 to: 9; + setSnakeFrom: 11 to: 5. + jack := SLPlayer new name: 'Jack'. + jill := SLPlayer new name: 'Jill'. + game addPlayer: jill. + game addPlayer: jack. + "jill moves" + game playOneStepWithRoll: 9. + self assert: jill position equals: 10. + self assert: ((game tileAt: 10) includesPlayers: jill). + "jack moves" + game playOneStepWithRoll: 2. + "jill tries to move but in fact stays at her place" + game playOneStepWithRoll: 5. + self assert: jill position equals: 10. + self assert: ((game tileAt: 10) includesPlayer: jill). + self deny: game isOver. ``` ### Variations As you see this single game has multiple variations. Here are some of the ones you may want to implement: - A player who lands on an occupied tile must go back to its originating tile. - If you roll a number higher than the number of tiles needed to reach the last square, you must continue moving backwards from the end. You will see that such extensions can be implemented in different ways. We suggest to avoid conditions in favor of defining objects responsible for each behavior variations. ### Conclusion This chapter went step by step to the process of getting from requirements to an actual implementation covered by tests. This chapter shows that design is an iterative process. What is also important is that without tests we would be a lot more worried about breaking something without be warned immediately. With tests we were able to change some parts of the design and rapidly make sure that the previous specification still hold. This chapter shows that identifying objects and their interactions is not always straightforward and multiple designs are often valid. \ No newline at end of file diff --git a/Chapters/SnakesAndLadders/SnakesAndLaddersSolution.md b/Chapters/SnakesAndLadders/SnakesAndLaddersSolution.md new file mode 100644 index 0000000..80566f3 --- /dev/null +++ b/Chapters/SnakesAndLadders/SnakesAndLaddersSolution.md @@ -0,0 +1,66 @@ +## Snake and ladder solutions ### A first real test ``` SLGame >> tileNumber: aNumber + + tiles := Array new: aNumber. + 1 to: aNumber do: [ :i | + tiles at: i put: (SLTile new position: i) ] ``` ``` SLGame >> tileNumber + + ^ tiles size ``` ### Accessing on tile ``` SLGame >> tileAt: aNumber + + ^ tiles at: aNumber ``` ### Adding players ``` SLPlayer >> name: aString + name := aString ``` ``` Object subclass: #SLTile + instanceVariableNames: 'position players' + classVariableNames: '' + package: 'SnakesAndLadders' ``` ``` SLGame >> initialize + players := OrderedCollection new. ``` ``` SLTile >> addPlayer: aPlayer + players add: aPlayer ``` ``` SLTile >> players + ^ players ``` ### Displaying players ``` SLPlayer >> printOn: aStream + aStream << '<' << name << '>' ``` ### Avoid leaking implementation information ``` SLTile >> includesPlayer: aPlayer + ^ players includes: aPlayer ``` ### Preparing to move players ``` SLPlayer >> position + ^ position ``` ``` SLPlayer >> position: anInteger + position := anInteger ``` ``` Object subclass: #SLPlayer + instanceVariableNames: 'name position' + classVariableNames: '' + package: 'SnakesAndLadders' ``` ``` SLGame >> addPlayer: aPlayer + aPlayer position: 1. + (tiles at: 1) addPlayer: aPlayer ``` ``` SLGame >> tileFor: aPlayer atDistance: aNumber + + ^ self tileAt: (aPlayer position + aNumber) ``` ### Finding the tile of a player ``` SLGame >> tileOfPlayer: aSLPlayer + ^ tiles at: aSLPlayer position ``` ### Moving a player ``` SLTile >> removePlayer: aPlayer + players remove: aPlayer ``` ``` SLGame >> movePlayer: aPlayer distance: anInteger + | targetTile | + targetTile := self tileFor: aPlayer atDistance: anInteger. + (self tileOfPlayer: aPlayer) removePlayer: aPlayer. + targetTile addPlayer: aPlayer. + aPlayer position: targetTile position. ``` ### New printing hook ``` SLAbstractTile >> printOn: aStream + + aStream << '['. + self printInsideOn: aStream. + aStream << ']' ``` ``` SLLadderTile >> printInsideOn: aStream + + super printInsideOn: aStream. + aStream << '->'. + targetTile position printOn: aStream ``` ### Snake and ladder declaration ``` SLGame >> setSnakeFrom: aSourcePosition to: aTargetPosition + + tiles + at: aSourcePosition + put: (SLSnakeTile new + position: aSourcePosition; + to: (tiles at: aTargetPosition) ; yourself) ``` ``` SLGame >> setLadderFrom: aSourcePosition to: aTargetPosition + + tiles + at: aSourcePosition + put: (SLLadderTile new + position: aSourcePosition ; + to: (tiles at: aTargetPosition) ; yourself) ``` ### Active tile actions ``` SLActiveTile >> acceptPlayer: aPlayer + targetTile acceptPlayer: aPlayer ``` ### Player turns and current player ``` Object subclass: #SLGame + instanceVariableNames: 'tiles players turn' + classVariableNames: '' + package: 'SnakesAndLadders' ``` ``` SLGame >> initialize + players := OrderedCollection new. + turn := 0 ``` ### Game end ``` SLGame >> isOver + + ^ players anySatisfy: [ :each | each position = tiles size ] ``` ### Playing one move ``` SLGame >> canMoveToPosition: aNumber + "We can only move if we stay within the game. + This implies that the player should draw an exact number to land on the finish tile." + + ^ aNumber <= tiles size ``` \ No newline at end of file diff --git a/Chapters/SyntaxGlimpse/SyntaxGlimpse.md b/Chapters/SyntaxGlimpse/SyntaxGlimpse.md new file mode 100644 index 0000000..6fc10d2 --- /dev/null +++ b/Chapters/SyntaxGlimpse/SyntaxGlimpse.md @@ -0,0 +1,83 @@ +## Pharo's syntax overview @cha:syntax Pharo syntax is minimal: The complete syntax fits in one postcard. Pharo syntax is based on a couple of constructs such as _variable definition_, ' _assignment_, _return_, _sequence_, and two main construct used systematically: _messages_ and _blocks_. This chapter provides some basic introduction to the key points. There will be not much more but the reader willing to get a deep and precise presentation of syntax should read the corresponding chapters in Pharo By Example available at [http://books.pharobyexample.org](http://books.pharobyexample.org) ### Minimal Pharo's syntax is minimal. Essentially there is syntax only for sending messages \(i.e., expressions\). Expressions are built up from a very small number of primitive elements \(message sends, assignments, closures, returns...\). There are only a couple a special variables, and there is no syntax for control structures or declaring new classes. Instead, nearly everything is achieved by sending messages to objects. For instance, instead of an if-then-else control structure, conditionals are expressed as messages \(such as `ifTrue:`\) sent to Boolean objects. New \(sub-\)classes are created by sending a message to their superclass. Although Pharo's message syntax is simple, it is unconventional. This chapter offers some guidance to help you get acclimatized to this special syntax for sending messages. If you already feel comfortable with the syntax, you may choose to skip this chapter, or come back to it later. ### Literal objects In Pharo, most objects are created by sending the message `new` to a class. There are also some objects that are directly created by the compiler. #### Comments Comments are delimited by double quotes `"`. ``` "Hello" ``` #### Characters, Strings and Symbols Characters short form creation is following the pattern dollar sign followed by the letter. The expression `$A` is the character A. ``` $A +>>> $A ``` Strings are delimited by single quotes `'`. ``` 'Hello' +>>> 'Hello' ``` Strings are sequence of characters. ``` 'Hello' first +>>> $A ``` We can concatenate strings using the message `,`. ``` 'Hello ', 'Pharoers' +>>> 'Hello Pharoers' ``` Symbols are unique strings. They start with `#`. ``` #'Hello Pharoers' +>>> #'Hello Pharoers' ``` #### Booleans There are two Booleans `true` and `false`. ``` true not +>>> false ``` #### Arrays Arrays can be created as any other objects by sending the message `new` to the class `Array`. In addition, Pharo supports two other ways: `#( )` and `{ . }`. The expression `#(1 + 2 3)` creates an array with four objects: 1, `#+`, 2, and 3. ``` #(1 + 2 3) +>>> #(1 + 2 3) ``` The expression `{1 + 2}` creates an array with one object resulting from the expression `1 + 2`. ``` { 1 + 2} +>>> #(3) ``` The expression `{1 + 2 . 3 + 4}` creates an array with two objects resulting from the execution of expressions separated by period. ``` { 1 + 2 . 3 + 4} +>>> #(3 7) ``` ### Key expressions In Pharo, except for a few constructs \(`:= ^ . ; # () {} [ : | ]` \), everything else is a message send. There is no operators or control flow constructs, just messages sent to objects. Therefore you can define a message named `+` in your class. #### Variable definition and assignment The expression `| sum |` declares a local variable. The expression ` sum := 0` changes the value of the variable `sum` to 0. The assignment construct is `:=`. ``` | sum | +sum := 0 ``` #### Special variables There are several special variables: - `nil` is the is the undefined object. It is the default value of variables. - `nil` is the undefined object. It is the unique instance of the class `UndefinedObject`. Instance variables, class variables and local variables are initialized to `nil`. - `true` and `false` are the unique instances of the Boolean classes `True` and `False`. - `self` is the receiver of the message and executing method \(See Chapter *@cha:inheritance@*\). - `super` is the receiver of the message and executing method \(See Chapter *@cha:inheritance@*\). #### Returning an object By default a method returns the receiver of the message. If we want to return a different object we use the construct `^`. The expression `^ sum` returns the object pointed by the variable `sum`. ``` | sum | +sum := 0. +sum := sum + 1. +^ sum ``` #### Sequence of expressions `.` separates messages. ``` | a | +a := #(11 22 33). +a at: 2 put: 66. +a +>>>#(11 66 33) ``` ### Message structure As most computation in Pharo is done by message passing, correctly identifying messages is key to avoiding future mistakes. The following terminology will help us: - A message is composed of the message **selector** and the optional message arguments. - A message is sent to a **receiver**. - The combination of a message and its receiver is called a _message send_ as shown in Figure *@fig:firstScriptMessage@*. ![Message sends composed of a receiver, a method selector, and a set of arguments.](figures/message.png width=70&label=fig:firstScriptMessage) A message is always sent to a receiver, which can be a single literal, a block or a variable or the result of evaluating another message. ### Three kind of Messages There are three kinds of message: _unary_ \(without any argument\), _binary_ \(with two arguments\) and _keywords_ \(with arguments\). There are three kinds of messages in Pharo. This distinction has been made to reduce the number of mandatory parentheses. 1. _Unary_ messages take no argument. `1 factorial` sends the message `factorial` to the object 1. 1. _Binary_ messages take exactly one argument. `1 + 2` sends the message `+` with argument 2 to the object 1. 1. _Keyword_ messages take an arbitrary number of arguments. `2 raisedTo: 6 modulo: 10` sends the message consisting of the message selector `raisedTo:modulo:` and the arguments 6 and 10 to the object 2. ### Unary messages Unary message selectors consist of alphanumeric characters, and start with a lower case letter. Here is an example of unary message: ``` 20 factorial +>>> 2432902008176640000 ``` We send the unary message `factorial` to the number `20`. Unary means that there is no argument and that the selector is composed of letters. ### Binary messages Here is an example of binary message: ``` 12 + 15 +>>> 27 ``` We send the binary message `+` to the number `12` with the argument `15`. A binary message is defined by a binary selector: a selector written without alphabetic characters, i.e., such `+-/*=~ < >`. Binary message selectors consist of one or more characters from the following set: ``` + - / \ * ~ < > = @ % | & ? , ``` ### Keyword messages Keyword messages are messages with arguments. Keyword message selectors consist of a series of alphanumeric keywords, where each keyword starts with a lower-case letter and ends with a colon. For example to access the element of an array we send the message `at:` with as argument the index in the array. Arrays have their first element at index 1. ``` #(11 22 33) at: 2 +>>>22 ``` Another keyword-based message is the message `at:put:` to change the value of an array. Here we put the number 66 in the second position. ``` | a | +a := #(11 22 33). +a at: 2 put: 66. +a +>>>#(11 66 33) ``` Another example ``` 1 between: 0 and: 3 +>>> true ``` This message would expressed in C-like syntax as follows: ``` 1.betweenAnd(0,3) +>>> true ``` ### Execution order and parentheses The order in which messages are sent is determined by the type of message. There are just three types of messages: **unary**, **binary**, and **keyword +messages**. Pharo distinguishes such three types of messages to minimize the number of parentheses. - Unary messages are always sent first, then binary messages and finally keyword ones. - As in most languages, parentheses can be used to change the order of execution. These rules make Pharo code as easy to read as possible. And most of the time you do not have to think about the rules. ``` (4 + 4) * 5 +>>> 40 ``` ``` 2 raisedTo: 1 + 3 factorial +>>> 128 ``` First we send `factorial` to 3, then we send `+ 6` to 1, and finally we send `raisedTo: 7` to 2. Recall that we use the notation expression `-->` to show the result of evaluating an expression. Precedence aside, execution is strictly from left to right, so: ``` 1 + 2 * 3 +>>> 9 ``` return 9 and not 7. Parentheses must be used to alter the order of evaluation: ``` 1 + (2 * 3) +>>> 7 ``` ### Sending multiple messages to the same object Message sends may be composed with periods and semi-colons. A period separated sequence of expressions causes each expression in the series to be evaluated as a _statement_, one after the other. ``` Transcript cr. +Transcript show: 'hello world'. +Transcript cr ``` This will send `cr` to the `Transcript` object, then send it `show: 'hello world'`, and finally send it another `cr`. When a series of messages is being sent to the _same_ receiver, then this can be expressed more succinctly as a _cascade_. The receiver is specified just once, and the sequence of messages is separated by semi-colons: ``` Transcript + cr; + show: 'hello world'; + cr ``` This has precisely the same effect as the previous example. ### Blocks Blocks \(lexical closures\) provide a mechanism to defer the execution of expressions. A block is essentially an anonymous function with a definition context. A block is executed by sending it the message `value`. The block answers the value of the last expression in its body, unless there is an explicit return \(with ^\) in which case it returns the value of the returned expression. ``` [ 1 + 2 ] value +>>> 3 ``` ``` [ 3 = 3 ifTrue: [ ^ 33 ]. 44 ] value +>>> 33 ``` Blocks may take parameters, each of which is declared with a leading colon. A vertical bar separates the parameter declaration\(s\) from the body of the block. To evaluate a block with one parameter, you must send it the message value: with one argument. A two-parameter block must be sent `value:value:`, and so on, up to 4 arguments. ``` [ :x | 1 + x ] value: 2 +>>> 3 +[ :x :y | x + y ] value: 1 value: 2 +>>> 3 ``` Blocks may also declare local variables, which are surrounded by vertical bars, just like local variable declarations in a method. Locals are declared after any arguments: ``` [ :x :y | + | z | + z := x + y. + z ] value: 1 value: 2 +>>> 3 ``` Blocks are actually lexical closures, since they can refer to variables of the surrounding environment. The following block refers to the variable `x` of its enclosing environment: ``` | x | +x := 1. +[ :y | x + y ] value: 2 +>>> 3 ``` Blocks are instances of the class `BlockClosure`. This means that they are objects, so they can be assigned to variables and passed as arguments just like any other object. ### Method syntax Whereas expressions may be evaluated anywhere in Pharo \(for example, in a playground, in a debugger, or in a browser\), methods are defined in a class using a code browser, or in the debugger. Methods can also be filed in from an external medium, but this is not the usual way to program in Pharo. Programs are developed one method at a time, in the context of a given class. A class is defined by sending a message to an existing class, asking it to create a subclass, so there is no special syntax required for defining classes. Here is the method `lineCount` in the class `String`. The usual _convention_ is to refer to methods as `ClassName>>methodName`. Here the method is then `String>>lineCount`. Note that `ClassName>>` is not part of the Pharo syntax just a convention used in books to clearly define a method. It means that when you define it you should just type the selector \(here `lineCount`\). ``` String >> lineCount + "Answer the number of lines represented by the receiver, where every cr adds one line." + + | cr count | + cr := Character cr. + count := 1 min: self size. + self do: [:c | c == cr ifTrue: [count := count + 1]]. + ^ count ``` Syntactically, a method consists of: 1. the method pattern, containing the name \(i.e., `lineCount`\) and any arguments \(none in this example\) 1. comments \(these may occur anywhere, but the convention is to put one at the top that explains what the method does\) 1. declarations of local variables \(i.e., `cr` and `count`\); and 1. any number of expressions separated by dots \(here there are four\). The execution of any expression preceded by a `^` \(a caret or upper arrow, which is Shift-6 for most keyboards\) will cause the method to exit at that point, returning the value of that expression. A method that terminates without explicitly returning some expression will implicitly return `self`. Arguments and local variables should always start with lower case letters. Names starting with upper-case letters are assumed to be global variables. Class names, like `Character`, for example, are simply global variables referring to the object representing that class. ### Some conditionals Pharo offers no special syntax for control constructs. Instead, these are typically expressed by sending messages to booleans, numbers and collections, with blocks as arguments. Conditionals are expressed by sending one of the messages `ifTrue:`, `ifFalse:` or `ifTrue:ifFalse:` to the result of a boolean expression. ``` (17 * 13 > 220) + ifTrue: [ 'bigger' ] + ifFalse: [ 'smaller' ] +>>>'bigger' ``` Note that the order can be reserved too. ``` (17 * 13 > 220) + ifFalse: [ 'smaller' ] + ifTrue: [ 'bigger' ] +>>>'bigger' ``` ### Some loops Loops are typically expressed by sending messages to blocks, integers or collections. Since the exit condition for a loop may be repeatedly evaluated, it should be a block rather than a boolean value. Here is an example of a very procedural loop: ``` n := 1. +[ n < 1000 ] whileTrue: [ n := n*2 ]. +n +>>> 1024 ``` The message `whileFalse:` reverses the exit condition. ``` n := 1. +[ n > 1000 ] whileFalse: [ n := n*2 ]. +n +>>> 1024 ``` The message `timesRepeat:` offers a simple way to implement a fixed iteration: ``` n := 1. +10 timesRepeat: [ n := n*2 ]. +n +>>> 1024 ``` We can also send the message `to:do:` to a number which then acts as the initial value of a loop counter. The two arguments are the upper bound, and a block that takes the current value of the loop counter as its argument: ``` result := String new. +1 to: 10 do: [:n | result := result, n printString, ' ']. +result +>>> '1 2 3 4 5 6 7 8 9 10' ``` ### Iterators Collections comprise a large number of different classes, many of which support the same set of messages. The most important messages for iterating over collections include `do:`, `collect:`, `select:`, `reject:`, `detect:` and `inject:into:`. These messages define high-level iterators that allow one to write very compact code. An **Interval** is a collection that lets one iterate over a sequence of numbers from the starting point to the end. `1 to: 10` represents the interval from 1 to 10. Since it is a collection, we can send the message `do:` to it. The argument is a block that is evaluated for each element of the collection. ``` result := String new. +(1 to: 10) do: [:n | result := result, n printString, ' ']. +result +>>> '1 2 3 4 5 6 7 8 9 10 ' ``` The message `collect:` builds a new collection of the same size, transforming each element. \(You can think of `collect:` as the Map in the MapReduce programming model\). ``` (1 to:10) collect: [ :each | each * each ] +>>> #(1 4 9 16 25 36 49 64 81 100) ``` The messages `select:` and `reject:` build new collections, each containing a subset of the elements satisfying \(or not\) the boolean block condition. The message `detect:` returns the first element satisfying the condition. Don't forget that strings are also collections \(of characters\), so you can iterate over all the characters. ``` 'hello there' select: [ :char | char isVowel ] +>>> 'eoee' +'hello there' reject: [ :char | char isVowel ] +>>> 'hll thr' +'hello there' detect: [ :char | char isVowel ] +>>> $e ``` More about collections can be found in Pharo by Example. ### Chapter summary - Pharo has some reserved identifiers \(also called pseudo-variables\): `true`, `false`, `nil`, `self` and `super`. - There are five kinds of literal objects: numbers \(5, 2.5, 1.9e15, 2r111\), characters \(`$a`\), strings \(`'hello'`\), symbols \(`#hello`\), and arrays \(`#('hello' #hi)` or `{ 1 . 2 . 1 + 2 }` \) - Strings are delimited by single quotes, comments by double quotes. To get a quote inside a string, double it. - Unlike strings, symbols are guaranteed to be globally unique. - Use `#( ... )` to define a literal array. Use `{ ... }` to define a dynamic array. - There are three kinds of messages: unary \(e.g., `1 asString`, `Array new`\), binary \(e.g., `3 + 4`, `'hi', ' there'`\), and keyword \(e.g., `'hi' at: 2 put: $o`\) - A cascaded message send is a sequence of messages sent to the same target, separated by semi-colons: - Local variables are declared with vertical bars. Use `:= ` for assignment. `|x| x := 1 ` - Expressions consist of message sends, cascades and assignments, evaluated left to right \(and optionally grouped with parentheses\). Statements are expressions separated by periods. - Block closures are expressions enclosed in square brackets. Blocks may take arguments and can contain temporary variables. The expressions in the block are not evaluated until you send the block a value message with the correct number of arguments. `[ :x | x + 2 ] value: 4` - There is no dedicated syntax for control constructs, just messages that conditionally evaluate blocks. \ No newline at end of file diff --git a/Chapters/SyntaxNutshell/SyntaxNutshell.md b/Chapters/SyntaxNutshell/SyntaxNutshell.md new file mode 100644 index 0000000..5c53515 --- /dev/null +++ b/Chapters/SyntaxNutshell/SyntaxNutshell.md @@ -0,0 +1,68 @@ +## Syntax in a nutshell @cha:syntax Pharo adopts a syntax very close to that of its ancestor, Smalltalk. The syntax is designed so that program text can be read aloud as though it were a kind of pidgin English. The following method of the class `Week` shows an example of the syntax. It checks whether `DayNames` already contains the argument, i.e. if this argument represents a correct day name. If this is the case, it will assign it to the variable `StartDay`. ``` startDay: aSymbol + + (DayNames includes: aSymbol) + ifTrue: [ StartDay := aSymbol ] + ifFalse: [ self error: aSymbol, ' is not a recognised day name' ] ``` Pharo's syntax is minimal. Essentially there is syntax only for sending messages \(i.e. expressions\). Expressions are built up from a very small number of primitive elements \(message sends, assignments, closures, returns...\). There are only 6 keywords, and there is no syntax for control structures or declaring new classes. Instead, nearly everything is achieved by sending messages to objects. For instance, instead of an if-then-else control structure, conditionals are expressed as messages \(such as `ifTrue:`\) sent to Boolean objects. New \(sub-\)classes are created by sending a message to their superclass. ### Syntactic elements Expressions are composed of the following building blocks: 1. The six reserved keywords, or _pseudo-variables_: `self`, `super`, `nil`, `true`, `false`, and `thisContext` 1. Constant expressions for _literal_ objects including numbers, characters, strings, symbols and arrays 1. Variable declarations 1. Assignments 1. Block closures 1. Messages We can see examples of the various syntactic elements in the Table below. | Syntax | What it represents | | --- | --- | | `startPoint` | a variable name | | `Transcript` | a global variable name | | self | pseudo-variable | | `1 ` | decimal integer | | `2r101` | binary integer | | `1.5` | floating point number | | `2.4e7` | exponential notation | | `$a` | the character `'a'` | | `'Hello'` | the string `'Hello'` | | `#Hello` | the symbol `#Hello` | | `#(1 2 3)` | a literal array | | `{ 1 . 2 . 1 + 2 }` | a dynamic array | | `"a comment"` | a comment | | `| x y |` | declaration of variables `x` and `y` | | `x := 1` | assign 1 to `x` | | `[:x | x + 2 ]` | a block that evaluates to `x + 2` | | `` | virtual machine primitive or annotation | | `3 factorial` | unary message `factorial` | | `3 + 4` | binary message `+` | | `2 raisedTo: 6 modulo: 10` | keyword message `raisedTo:modulo:` | | `^ true` | return the value true | | `x := 2 . x := x + x` | expression separator \(`.`\) | | `Transcript show: 'hello'; cr` | message cascade \(`;`\) | **Local variables.** `startPoint` is a variable name, or identifier. By convention, identifiers are composed of words in "camelCase" \(i.e., each word except the first starting with an upper case letter\). The first letter of an instance variable, method or block argument, or temporary variable must be lower case. This indicates to the reader that the variable has a private scope. **Shared variables.** Identifiers that start with upper case letters are global variables, class variables, pool dictionaries or class names. `Processor` is a global variable, an instance of the class `ProcessScheduler`. **The receiver.** `self` is a keyword that refers to the object inside which the current method is executing. We call it "the receiver" because this object has received the message that caused the method to execute. `self` is called a "pseudo-variable" since we cannot assign to it. **Integers.** In addition to ordinary decimal integers like 42, Pharo also provides a radix notation. `2r101` is 101 in radix 2 \(i.e., binary\), which is equal to decimal 5. **Floating point numbers.** can be specified with their base-ten exponent: `2.4e7` is `2.4 X 10^7`. **Characters.** A dollar sign introduces a literal character: `$a` is the literal for the character `'a'`. Instances of non-printing characters can be obtained by sending appropriately named messages to the `Character` class, such as `Character space` and `Character tab`. **Strings.** Single quotes `' '` are used to define a literal string. If you want a string with a single quote inside, just double the quote, as in `'G''day'`. **Symbols.** Symbols are like Strings, in that they contain a sequence of characters. However, unlike a string, a literal symbol is guaranteed to be globally unique. There is only one `Symbol` object `#Hello` but there may be multiple `String` objects with the value `'Hello'`. **Compile-time arrays.** are defined by `#( )`, surrounding space-separated literals. Everything within the parentheses must be a compile-time constant. For example, `#(27 (true false) abc)` is a literal array of three elements: the integer 27, the compile-time array containing the two booleans, and the symbol `#abc`. \(Note that this is the same as `#(27 #(true false) #abc)`.\) **Run-time arrays.** Curly braces `{ }` define a \(dynamic\) array at run-time. Elements are expressions separated by periods. So `{ 1. 2. 1 + 2 }` defines an array with elements 1, 2, and the result of evaluating `1+2`. **Comments.** are enclosed in double quotes " ". "hello" is a comment, not a string, and is ignored by the Pharo compiler. Comments may span multiple lines. **Local variable definitions.** Vertical bars `| |` enclose the declaration of one or more local variables in a method \(and also in a block\). **Assignment.** `:= ` assigns an object to a variable. **Blocks.** Square brackets `[ ]` define a block, also known as a block closure or a lexical closure, which is a first-class object representing a function. As we shall see, blocks may take arguments \(`[:i | ...]`\) and can have local variables. **Primitives.** `< primitive: ... >` denotes an invocation of a virtual machine primitive. For example, `< primitive: 1 >` is the VM primitive for `SmallInteger`. Any code following the primitive is executed only if the primitive fails. The same syntax is also used for method annotations \(pragmas\). **Unary messages.** These consist of a single word \(like `factorial`\) sent to a receiver \(like 3\). In `3 factorial`, 3 is the receiver, and `factorial` is the message selector. **Binary messages.** These are message with an argument and whose selector looks like mathematical expressions \(for example: `+`\) sent to a receiver, and taking a single argument. In `3 + 4`, the receiver is 3, the message selector is `+`, and the argument is 4. **Keyword messages.** They consist of multiple keywords \(like `raisedTo: +modulo:`\), each ending with a colon and taking a single argument. In the expression `2 raisedTo: 6 modulo: 10`, the message selector `raisedTo:modulo:` takes the two arguments 6 and 10, one following each colon. We send the message to the receiver 2. **Method return.** `^` is used to _return_ a value from a method. **Sequences of statements.** A period or full-stop \(`.`\) is the statement separator. Putting a period between two expressions turns them into independent statements. **Cascades.** Semicolons \( `;` \) can be used to send a cascade of messages to a single receiver. In `Transcript show: 'hello'; cr` we first send the keyword message `show: 'hello'` to the receiver `Transcript`, and then we send the unary message `cr` to the same receiver. The classes `Number`, `Character`, `String` and `Boolean` are described in more detail in Chapter *@cha:basicClasses@*. ### Pseudo-variables In Pharo, there are 6 reserved keywords, or pseudo-variables: `nil`, `true`, `false`, `self`, `super`, and `thisContext`. They are called pseudo-variables because they are predefined and cannot be assigned to. `true`, `false`, and `nil` are constants, while the values of `self`, `super`, and `thisContext` vary dynamically as code is executed. - `true` and `false` are the unique instances of the Boolean classes `True` and `False`. See Chapter *@cha:basicClasses@* for more details. - `self` always refers to the receiver of the currently executing method. - `super` also refers to the receiver of the current method, but when you send a message to `super`, the method-lookup changes so that it starts from the superclass of the class containing the method that uses `super`. For further details see Chapter *@cha:model@*. - `nil` is the undefined object. It is the unique instance of the class `UndefinedObject`. Instance variables, class variables and local variables are initialized to `nil`. - `thisContext` is a pseudo-variable that represents the top frame of the execution stack. `thisContext` is normally not of interest to most programmers, but it is essential for implementing development tools like the debugger, and it is also used to implement exception handling and continuations. ### Message sends There are three kinds of messages in Pharo. This distinction has been made to reduce the number of mandatory parentheses. 1. _Unary_ messages take no argument. `1 factorial` sends the message `factorial` to the object 1. 1. _Binary_ messages take exactly one argument. `1 + 2` sends the message `+` with argument 2 to the object 1. 1. _Keyword_ messages take an arbitrary number of arguments. `2 raisedTo: 6 modulo: 10` sends the message consisting of the message selector `raisedTo:modulo:` and the arguments 6 and 10 to the object 2. ##### Unary messages. Unary message selectors consist of alphanumeric characters, and start with a lower case letter. ##### Binary messages. Binary message selectors consist of one or more characters from the following set: ``` + - / \ * ~ < > = @ % | & ? , ``` ##### Keyword message selectors. Keyword message selectors consist of a series of alphanumeric keywords, where each keyword starts with a lower-case letter and ends with a colon. ##### Message precedence. Unary messages have the highest precedence, then binary messages, and finally keyword messages, so: ``` 2 raisedTo: 1 + 3 factorial +>>> 128 ``` First we send `factorial` to 3, then we send `+ 6` to 1, and finally we send `raisedTo: 7` to 2. Recall that we use the notation expression `-->` to show the result of evaluating an expression. Precedence aside, execution is strictly from left to right, so: ``` 1 + 2 * 3 +>>> 9 ``` return 9 and not 7. Parentheses must be used to alter the order of evaluation: ``` 1 + (2 * 3) +>>> 7 ``` ##### Periods and semi-colons. Message sends may be composed with periods and semi-colons. A period separated sequence of expressions causes each expression in the series to be evaluated as a _statement_, one after the other. ``` Transcript cr. +Transcript show: 'hello world'. +Transcript cr ``` This will send `cr` to the `Transcript` object, then send it `show: 'hello world'`, and finally send it another `cr`. When a series of messages is being sent to the _same_ receiver, then this can be expressed more succinctly as a _cascade_. The receiver is specified just once, and the sequence of messages is separated by semi-colons: ``` Transcript + cr; + show: 'hello world'; + cr ``` This has precisely the same effect as the previous example. ### Method syntax Whereas expressions may be evaluated anywhere in Pharo \(for example, in a playground, in a debugger, or in a browser\), methods are normally defined in a browser window, or in the debugger. Methods can also be filed in from an external medium, but this is not the usual way to program in Pharo. Programs are developed one method at a time, in the context of a given class. A class is defined by sending a message to an existing class, asking it to create a subclass, so there is no special syntax required for defining classes. Here is the method `lineCount` in the class `String`. The usual _convention_ is to refer to methods as ClassName>>methodName. Here the method is then String>>lineCount. Note that ClassName>>methodName is not part of the Pharo syntax just a convention used in books to clearly define a method. ``` String >> lineCount + "Answer the number of lines represented by the receiver, where every cr adds one line." + + | cr count | + cr := Character cr. + count := 1 min: self size. + self do: [:c | c == cr ifTrue: [count := count + 1]]. + ^ count ``` Syntactically, a method consists of: 1. the method pattern, containing the name \(i.e., `lineCount`\) and any arguments \(none in this example\) 1. comments \(these may occur anywhere, but the convention is to put one at the top that explains what the method does\) 1. declarations of local variables \(i.e., `cr` and `count`\); and 1. any number of expressions separated by dots \(here there are four\) The execution of any expression preceded by a `^` \(a caret or upper arrow, which is Shift-6 for most keyboards\) will cause the method to exit at that point, returning the value of that expression. A method that terminates without explicitly returning some expression will implicitly return `self`. Arguments and local variables should always start with lower case letters. Names starting with upper-case letters are assumed to be global variables. Class names, like `Character`, for example, are simply global variables referring to the object representing that class. ### Block syntax Blocks \(lexical closures\) provide a mechanism to defer the execution of expressions. A block is essentially an anonymous function with a definition context. A block is executed by sending it the message `value`. The block answers the value of the last expression in its body, unless there is an explicit return \(with ^\) in which case it returns the value of the returned expression. ```language=smalltalk [ 1 + 2 ] value +>>> 3 ``` ```language=smalltalk [ 3 = 3 ifTrue: [ ^ 33 ]. 44 ] value +>>> 33 ``` Blocks may take parameters, each of which is declared with a leading colon. A vertical bar separates the parameter declaration\(s\) from the body of the block. To evaluate a block with one parameter, you must send it the message value: with one argument. A two-parameter block must be sent `value:value:`, and so on, up to 4 arguments. ```language=smalltalk [ :x | 1 + x ] value: 2 +>>> 3 +[ :x :y | x + y ] value: 1 value: 2 +>>> 3 ``` If you have a block with more than four parameters, you must use `valueWithArguments:` and pass the arguments in an array. \(A block with a large number of parameters is often a sign of a design problem.\) Blocks may also declare local variables, which are surrounded by vertical bars, just like local variable declarations in a method. Locals are declared after any arguments: ```language=smalltalk [ :x :y | + | z | + z := x + y. + z ] value: 1 value: 2 +>>> 3 ``` Blocks are actually lexical closures, since they can refer to variables of the surrounding environment. The following block refers to the variable `x` of its enclosing environment: ``` | x | +x := 1. +[ :y | x + y ] value: 2 +>>> 3 ``` Blocks are instances of the class `BlockClosure`. This means that they are objects, so they can be assigned to variables and passed as arguments just like any other object. ### Some conditionals Pharo offers no special syntax for control constructs. Instead, these are typically expressed by sending messages to booleans, numbers and collections, with blocks as arguments. Conditionals are expressed by sending one of the messages `ifTrue:`, `ifFalse:` or `ifTrue:ifFalse:` to the result of a boolean expression. See Chapter *@cha:basicClasses@*, for more about booleans. ``` 17 * 13 > 220 + ifTrue: [ 'bigger' ] + ifFalse: [ 'smaller' ] +>>>'bigger' ``` ### Some loops Loops are typically expressed by sending messages to blocks, integers or collections. Since the exit condition for a loop may be repeatedly evaluated, it should be a block rather than a boolean value. Here is an example of a very procedural loop: ``` n := 1. +[ n < 1000 ] whileTrue: [ n := n*2 ]. +n +>>> 1024 ``` `whileFalse:` reverses the exit condition. ``` n := 1. +[ n > 1000 ] whileFalse: [ n := n*2 ]. +n +>>> 1024 ``` `timesRepeat:` offers a simple way to implement a fixed iteration: ``` n := 1. +10 timesRepeat: [ n := n*2 ]. +n +>>> 1024 ``` We can also send the message `to:do:` to a number which then acts as the initial value of a loop counter. The two arguments are the upper bound, and a block that takes the current value of the loop counter as its argument: ``` result := String new. +1 to: 10 do: [:n | result := result, n printString, ' ']. +result +>>> '1 2 3 4 5 6 7 8 9 10 ' ``` ### High-order iterators Collections comprise a large number of different classes, many of which support the same protocol. The most important messages for iterating over collections include `do:`, `collect:`, `select:`, `reject:`, `detect:` and `inject:into:`. These messages define high-level iterators that allow one to write very compact code. An **Interval** is a collection that lets one iterate over a sequence of numbers from the starting point to the end. `1 to: 10` represents the interval from 1 to 10. Since it is a collection, we can send the message `do:` to it. The argument is a block that is evaluated for each element of the collection. ``` result := String new. +(1 to: 10) do: [:n | result := result, n printString, ' ']. +result +>>> '1 2 3 4 5 6 7 8 9 10 ' ``` `collect:` builds a new collection of the same size, transforming each element. \(You can think of `collect:` as the Map in the MapReduce programming model\). ``` (1 to:10) collect: [ :each | each * each ] +>>> #(1 4 9 16 25 36 49 64 81 100) ``` `select:` and `reject:` build new collections, each containing a subset of the elements satisfying \(or not\) the boolean block condition. `detect:` returns the first element satisfying the condition. Don't forget that strings are also collections \(of characters\), so you can iterate over all the characters. ``` 'hello there' select: [ :char | char isVowel ] +>>> 'eoee' +'hello there' reject: [ :char | char isVowel ] +>>> 'hll thr' +'hello there' detect: [ :char | char isVowel ] +>>> $e ``` Finally, you should be aware that collections also support a functional-style fold operator in the `inject:into:` method. You can also think of it as the Reduce in the MapReduce programming model. This lets you generate a cumulative result using an expression that starts with a seed value and injects each element of the collection. Sums and products are typical examples. ``` (1 to: 10) inject: 0 into: [ :sum :each | sum + each] +>>> 55 ``` This is equivalent to `0+1+2+3+4+5+6+7+8+9+10`. More about collections can be found in Chapter *@cha:collections@*. ### Primitives and pragmas In Pharo everything is an object, and everything happens by sending messages. Nevertheless, at certain points we hit rock bottom. Certain objects can only get work done by invoking virtual machine primitives. For example, the following are all implemented as primitives: memory allocation \(`new`, `new:`\), bit manipulation \(`bitAnd:`, `bitOr:`, `bitShift:`\), pointer and integer arithmetic \(+, -, <, >, *, /, , =, ==...\), and array access \(`at:`, `at:put:`\). Primitives are invoked with the syntax ``. A method that invokes such a primitive may also include Pharo code, which will be executed only if the primitive fails. Here we see the code for `SmallInteger>>+`. If the primitive fails, the expression `super + aNumber` will be executed and returned. ``` + aNumber + "Primitive. Add the receiver to the argument and answer with the result + if it is a SmallInteger. Fail if the argument or the result is not a + SmallInteger Essential No Lookup. See Object documentation whatIsAPrimitive." + + + ^ super + aNumber ``` In Pharo, the angle bracket syntax is also used for method annotations called pragmas. ### Chapter summary - Pharo has only six reserved identifiers \(also called pseudo-variables\): `true`, `false`, `nil`, `self`, `super`, and `thisContext`. - There are five kinds of literal objects: numbers \(5, 2.5, 1.9e15, 2r111\), characters \(`$a`\), strings \(`'hello'`\), symbols \(`#hello`\), and arrays \(`#('hello' #hi)` or `{ 1 . 2 . 1 + 2 }` \) - Strings are delimited by single quotes, comments by double quotes. To get a quote inside a string, double it. - Unlike strings, symbols are guaranteed to be globally unique. - Use `#( ... )` to define a literal array. Use `{ ... }` to define a dynamic array. ```language=smalltalk #(1+2) size +>>> 3 +{1+2} size +>>>1 ``` - There are three kinds of messages: unary \(e.g., `1 asString`, `Array new`\), binary \(e.g., `3 + 4`, `'hi', ' there'`\), and keyword \(e.g., `'hi' at: 2 put: $o`\) - A cascaded message send is a sequence of messages sent to the same target, separated by semi-colons: ``` OrderedCollection new add: #calvin; add: #hobbes; size +>>> 2 ``` - Local variables are declared with vertical bars. Use `:= ` for assignment. `|x| x := 1 ` - Expressions consist of message sends, cascades and assignments, evaluated left to right \(and optionally grouped with parentheses\). Statements are expressions separated by periods. - Block closures are expressions enclosed in square brackets. Blocks may take arguments and can contain temporary variables. The expressions in the block are not evaluated until you send the block a value message with the correct number of arguments. `[ :x | x + 2 ] value: 4` - There is no dedicated syntax for control constructs, just messages that conditionally evaluate blocks. \ No newline at end of file diff --git a/Chapters/Tamagoshi/Tamagoshi.md b/Chapters/Tamagoshi/Tamagoshi.md new file mode 100644 index 0000000..4fae38e --- /dev/null +++ b/Chapters/Tamagoshi/Tamagoshi.md @@ -0,0 +1,223 @@ +## A Digital Animal: A gluttonous Tamagotchi In this chapter we propose you to develop a small digital animal that has its own life and requires care from you. People were really fond of digital animals called tamagotchi. Therefore we will build one step by step. This project is a pretext for revisiting the basic actions to define a class, instance variables, messages and methods. We will also discuss encapsulation. ### A gluttonous Tamagotchi We have to decide the behavior that our digital monster should have. Here is the list of the behavior we propose you to implement for our tamagoshi. % The Figure~\ref{fig:tomastate} represents how the tomagoshi changes its state. - It can eat and digest food. It can be hungry when its tummy is empty and satisfied when it eats enough food. - It has its own life cycle with its own nights and days. It goes to sleep at night and wake up the morning. - It is gluttonous and selfish so falls asleep as soon as it eats enough. - Its change color depending on its mood. We choose some colors to describe its state: - blue when it is satisfied possibly sleeping, - black when sleeping but hungry, - green when wakened up, - and red when it is hungry. % \includegraphics[width=8cm]{figures/tomastate} ### Define Tamagotchi class Define a new package named for example 'Tamagotchi' and define the class `Tamagoshi`. As we would like to be able to interact with it and have a graphical representation, we define the class `Tamagotchi` as a subclass of the class `Morph`. A morph is a graphical entity in Pharo. It knows how to draw itself on the screen. ``` Morph subclass: #Tamagotchi + instanceVariableNames: '' + classVariableNames: '' + package: 'Tamagoshi' ``` We edit the class comment and write something in the vein of: ``` I represent a tamagotchi: A small virtual animal that has its own life. ``` Executing the following snippet should get you a blue square as shown in *@squareTamagoshi@*. Of course this is not really exciting but this is a start. ``` | t | +t := Tamagotchi new. +t openInHand ``` % +A square and rudimentary tamagotchi>file://figures/squareTamagoshi.pdf|width=40|label=squareTamagoshi+ ![Our tamagotchi says hello in the Transcript. ](figures/tamagotchiAndHello.png width=50&label=squareTamagoshi) #### Open a Transcript We will use the Transcript little window as a way to get some information from the tamagoshi. You can open a transcript using the tools menu or programmatically as follows: ``` Transcript open ``` The method `step` of a morph is called by the system at regular interval. We can redefine the method `step` of `Morph` as follows and we will obtain the situation depicted by Figure *@squareTamagoshi@*. ``` Tamagoshi >> step + "Print some information on the output" + self logCr: 'hello' ``` We should also define the interval between two steps, by defining the method `stepTime`. ``` Tamagotchi >> stepTime + "Return the time interval between two outputs" + ^ 500 ``` ### Interacting with our live friend Before continuing any further, we want to show you some ways to interact with the tamagotchi. #### Halos Pharo offers little graphical menus called halos. To bring them, click and press option, command/control and shift. ![You can bring the halos around your tamagotchi by clicking on it while pressing option + command/control + shift ](figures/TamaWithHalo.pdf width=50&label=TamaWithHalo) With the halos you can - delete your tamagotchi. Pay attention you cannot bring it back. - move it - bring a menu and get an inspector as shown in Figure *@TamaWithInspectMenu@*. ![Bringing an inspector to interact with a tamgotchi.](figures/TamaWithInspectMenu.pdf width=50&label=TamaWithInspectMenu) Note that it is easier to bring an inspector while creating the tamagotchi using the message `inspect` as follows: ``` | t | +t := Tamagotchi new. +t openInHand. +t inspect ``` #### Better use the playground You can also use the menu item do it and go of the playground to obtain an integrated inspector. In Figure *@TamaWithInspectorAndInstruction@*. We created a tamagotchi and we selected **do it and go** and we ask the tamagotchi to display itself. ![Using Playground to get an inspector on a tamagotchi and sending it the message `openInHand`. ](figures/TamaWithInspectorAndInstruction.png width=100&label=TamaWithInspectorAndInstruction) ### Adding a Name ``` Morph subclass: #Tamagoshi + instanceVariableNames: 'name' + classVariableNames: '' + category: 'Tamagoshi' ``` Make sure that if we do not give a name it is named `'pika'`. When we create a new object by sending the `new` message, the message `initialize` is sent. So we redefine it to put a new value with the string `'pika'`. We use the expression `super initialize` to make sure that the Morph is well initialized. This concept will be explained later in the book. ``` Tamagotchi >> initialize + super initialize. + name := 'pika' ``` When we add a new instance variable to a class, all the existing instances get this instance variable but it will not be initialized correctly. So the best is to kill the tamagotchi and recreate one. You can kill a tamagotchi either using the halos or sending the message `delete` using the inspector. ![TamaWithName](figures/TamaWithName.png width=50&label=TamaWithName) ![withPopUp](figures/withPopUp.pdf width=50&label=withPopUp) ``` UIManager default request: 'Give a name to your tamagoshi' ``` ![requestInTranscript](figures/requestInTranscript.pdf width=50&label=requestInTranscript) ``` Tamagotchi >> nameMe + name := UIManager default request: 'Give a name please!' ``` ``` Tamagotchi >> step + self logCr: 'hello, my name is ', name ``` ![inspectorWithFifiPrompt](figures/inspectorWithFifiPrompt.pdf width=50&label=inspectorWithFifiPrompt) ![promptWithNil](figures/promptWithNil.pdf width=50&label=promptWithNil) ### Making nameMe more robust ``` Tamagotchi >> nameMe + + | newName | + newName := UIManager default request: 'Give a name please'. + newName isNil + ifTrue: [ ] + ifFalse: [ name := newName ] ``` ``` Tamagotchi >> nameMe + + | newName | + newName := UIManager default request: 'Give a name please'. + newName isNotNil + ifTrue: [ name := newName ] ``` ### Freeze ``` Tamagotchi >> freeze + "Freeze the behavior of the receiver until it receives the message unfreeze." + self stopStepping ``` ``` Tamagotchi >> unfreeze + "Get the receiver acting again." + self startStepping ``` ![LargerRounder](figures/LargerRounder.pdf width=50&label=LargerRounder) `drawOn:` controls the drawing of the Morph. We can for example add an oval. ``` drawOn: aCanvas + + super drawOn: aCanvas. + aCanvas fillOval: self bounds + fillStyle: self fillStyle + borderWidth: 3 + borderColor: Color white. ``` ![WithEllipse](figures/WithEllipse.pdf width=50&label=WithEllipse) ``` eyesOn: aCanvas + + | b m y | + b := self bounds. + m := b width / 2. + y := b width / 4. + aCanvas + fillOval: ((b origin + (m - 20 @ y)) extent: 10@10) + fillStyle: self fillStyle + borderWidth: 3 + borderColor: Color white. + aCanvas + fillOval: ((b origin + (m + 15 @ y)) extent: 10@10) + fillStyle: self fillStyle + borderWidth: 3 + borderColor: Color white. ``` ``` drawOn: aCanvas + + super drawOn: aCanvas. + self eyesOn: aCanvas ``` ![justEyes](figures/justEyes.pdf width=50&label=justEyes) Now we can define the mouth of our digital animal by drawing two lines like in the following and we obtain Figure\~ref{fig:Smiling}. ``` mouthOn: aCanvas + + | b m y middlePoint | + b := self bounds. + m := b width / 2. + y := b width / 1.8. + middlePoint := b origin + ( m@y ). + aCanvas + line: b origin + ((m-15) @ (y -5)) to: middlePoint width: 3 color: Color black. + aCanvas + line: b origin + ((m+15) @ (y -5)) to: middlePoint width: 3 color: Color black. ``` ``` drawOn: aCanvas + + super drawOn: aCanvas. + self eyesOn: aCanvas. + self mouthOn: aCanvas. ``` ![A smiling tamagoshi.](figures/Smiling.pdf width=50&label=Smiling) ### Eating Behavior Let us add a tummy to our tamagoshi. ``` Morph subclass: #Tamagoshi + instanceVariableNames: 'name tummy' + classVariableNames: '' + category: 'Tamagoshi' ``` ``` initialize + + super initialize. + self useRoundedCorners. + self extent: 100@80. + name := 'pika'. + tummy := 5. ``` Now we can also change the printing behavior to show its tummy. ``` step + + self logCr: 'hello, my name is ', name, ' and my tummy is: ', tummy printString. ``` ``` tummyOn: aCanvas + aCanvas + drawString: tummy printString + at: self bounds corner - (20 @ 20) + font: nil + color: Color yellow ``` ![withbBelly](figures/withbBelly.pdf width=50&label=withbBelly) do not forget to change the drawOn: method to invoke the tummyOn: method. ``` drawOn: aCanvas + + super drawOn: aCanvas. + self eyesOn: aCanvas. + self mouthOn: aCanvas. + self tummyOn: aCanvas. ``` Now we would like to define the method eat so that we can feed our tamagoshi. ``` eat + + tummy := tummy + 1 ``` ![Now we show the food items in the tamagoshi' tummy](figures/eating.pdf width=50&label=eating) ``` eat + + tummy := tummy + 1. + self changed. ``` Now we will tell the morph that it should react to mouse events. ``` handlesMouseDown: evt + "Returning true means that the morph can react to mouse click" + + ^ true ``` And we will make sure that when we click on it, it gets fed. ``` mouseDown: evt + + self eat. ``` Now it may be wise to limit the stomach capability of our digital friend. We revise then the eat method to only increase it if we did not reach a limit. ``` eat + + tummy < 55 + ifTrue: [ tummy := tummy + 1 + self changed]. ``` ### Digesting Extract the code of the step method into a new method name talk. ``` talk + + self logCr: 'hello, my name is ', name, ' and my tummy is: ', tummy printString. ``` ``` step + + self talk. ``` Now we are ready to make our tamagoshi digest its food. ``` digest + + tummy := tummy - 1. + self changed. ``` Try with the inspector to send some digest messages to your tamagoshi. Now this is annoying to add the changed message everywhere so we introduce a method named: ``` tummy: aNumber + + tummy := aNumber. + self changed. ``` and we use it in eat and digest ``` digest + + self tummy: tummy - 1. ``` ``` eat + + tummy < 55 + ifTrue: [ self tummy: tummy + 1 ]. ``` ``` step + + self talk. + self digest. ``` You will quickly see that digest is wrong because we will get negative numbers really fast. So first we change the definition of digest to be ``` digest + tummy = 0 + ifFalse: [ self tummy: tummy - 1] ``` Now since it is digesting too fast we should think how we could make our tamagoshi digest slower. What we need is a counter of the tick passed and based on that digest or not. So we add the hours instance variable to the class. ``` Morph subclass: #Tamagoshi + instanceVariableNames: 'name tummy hours' + classVariableNames: '' + category: 'Tamagoshi' ``` ``` initialize + + super initialize. + self useRoundedCorners. + self extent: 100@80. + name := 'pika'. + tummy := 5. + hours := 0. ``` ``` nextHour + + hours := hours + 1 ``` ``` step + + self talk. + self nextHour. + self digest ``` Now we can make our tamagoshi digest slower by simply making sure that several hours passed before it digests. Just checking for example if the number of hours is divisible b a given number will do the trick. ``` step + + self talk. + self nextHour. + (hours isDivisibleBy: 3) + ifTrue: [self digest ] ``` ### Hungry Now we get ready to make our tamagoshi changing face when he is hungry. ``` isHungry + + ^ tummy isZero ``` We can now rewrite digest using isHungry as follow: ``` digest + self isHungry + ifFalse: [ self tummy: tummy - 1] ``` ``` hungryMouthOn: aCanvas + + | b m y middlePoint | + b := self bounds. + m := b width / 2. + y := b width / 1.8. + middlePoint := b origin + ( m@y ). + aCanvas + line: b origin + ((m-15) @ (y + 5)) to: middlePoint width: 3 color: Color black. + aCanvas + line: b origin + ((m+15) @ (y + 5)) to: middlePoint width: 3 color: Color black. ``` ![Our tamagoshi shows now that he is hungry](figures/hungry.pdf width=50&label=hungry) Now we can change a bit the messages send by our tamagoshi. ``` talk + + self isHungry + ifTrue: [ self logCr: 'Please feed me' ] + ifFalse: [ self logCr: 'hello, my name is ', name, ' and I''m ok']. ``` ### Nights and days Now we want to manage the night and day period of our tamagoshi. ``` Morph subclass: #Tamagoshi + instanceVariableNames: 'name tummy hours isNight' + classVariableNames: '' + category: 'Tamagoshi' ``` ``` initialize + + super initialize. + self useRoundedCorners. + self extent: 100@80. + name := 'pika'. + tummy := 5. + hours := 0. + isNight := false ``` ``` isNight + + ^ isNight ``` ``` switchDayPeriod + "Switch from night to day and day to night" + isNight := isNight not. ``` ``` checkAndNextDayPeriod + "Switch from night to day and day to night when necessary" + + (hours isDivisibleBy: 12) + ifTrue: [ self switchDayPeriod ] ``` We can change the messages to show that our animal is sleeping ``` talk + self isNight + ifTrue: [ self logCr: 'ZZZzzzzzzz' ] + ifFalse: [ + self isHungry + ifTrue: [ self logCr: 'Please feed me' ] + ifFalse: [ self logCr: 'hello, my name is ' , name , ' and I''m ok' ] ] ``` As we will see later, these conditionals all over the place are not that nice, each time we add a new behavior we have to change talk and the drawOn: method logic becomes more and more complex. ``` sleepyEyesOn: aCanvas + + | b m y | + b := self bounds. + m := b width / 2. + y := b width / 4. + aCanvas + fillOval: ((b origin + (m - 20 @ y)) extent: 11@5) + fillStyle: self fillStyle + borderWidth: 3 + borderColor: Color white. + aCanvas + fillOval: ((b origin + (m + 15 @ y)) extent: 11@5) + fillStyle: self fillStyle + borderWidth: 3 + borderColor: Color white. ``` ``` drawOn: aCanvas + super drawOn: aCanvas. + self isNight + ifTrue: [ + self sleepyEyesOn: aCanvas. + self color: Color darkGray ] + ifFalse: [ + self eyesOn: aCanvas. + self isHungry + ifTrue: [ + self color: Color red. + self hungryMouthOn: aCanvas ] + ifFalse: [ + self color: Color blue. + self mouthOn: aCanvas ] ]. + self bellyOn: aCanvas. + self changed. ``` ![sleeping](figures/sleeping.pdf width=50&label=sleeping) ### Further Experiments Now we propose you different modifications to change the behavior of your tomagoshi. - Make that we can only feed the tomagoshi the day. - Make it die when it is starving from too long time \(hints you can use the method `delete` or `stopStepping`. - Make it happy, you could make it sings. - Change its graphical representation. \ No newline at end of file diff --git a/Chapters/Tests/Tests.md b/Chapters/Tests/Tests.md new file mode 100644 index 0000000..0e49403 --- /dev/null +++ b/Chapters/Tests/Tests.md @@ -0,0 +1,68 @@ +## Tests, tests and tests @cha:sunit In this chapter we start by showing that tests are simple. Second we present test driven design - basically what we will try to do systematically in this book. Then we discuss why we test, and what makes a good test. We then present a series of small examples showing how to use SUnit. ### Writing a test in 2 minutes A test is a context, a stimulus and an assertion \(verification that we get the correct state\). Here is an example on sets. Remember that sets are mathematical entities having only one occurrence of their elements. First we test that adding an element changes the size of the set. - **Context:** we take an empty set. - **Stimulus:** we add `$A` into the empty set. - **Assertion:** the set has one element. Another test is that a set only contains only one occurence of one element. - **Context:** we take an empty set. - **Stimulus:** we add `$A` into the empty set. - **Assertion:** the set has one element. - **Stimulus:** we add `$A` into the empty set. - **Assertion:** the set has still one element. #### How do we declare a test in Pharo? This is really easy to declare one test: we define one class \(that will host multiple test definitions\) and one method per test. Here the class `MyExampleSetTest` should inherit from `TestCase`. It is the place to define the tests related to the class `Set`. ``` TestCase subclass: #MyExampleSetTest + instanceVariableNames: '' + classVariableNames: '' + package: 'MySetTest' ``` Now we can define one test expression as a method. There is one constraint: the method selector should start with `test`. ``` MyExampleSetTest >> testAddTwice + | s | + s := Set new. + self assert: s isEmpty. + s add: $A. + self assert: s size equals: 1. + s add: $A. + self assert: s size equals: 1. ``` Then using the Test runner or pressing on icons of the Pharo browser \(as shown in Figure *@fig:browsertests@*\), you will be able to execute the method `testAddTwice` and it will tell you if it passes or fails \(i.e., if its assertions are true\). Now that you know that writing a test is not complex. Let us look a bit at the theory before going into more details. !!coffee A test is a context, a stimulus and an _assertion_ \(verification that we get the correct state\). ### Test Driven Design The interest in testing and Test Driven Development is not limited to Pharo. Automated testing has become a hallmark of the _Agile software +development_ movement, and any software developer concerned with improving software quality would do well to adopt it. Indeed, developers in many languages have come to appreciate the power of unit testing. Neither testing, nor the building of test suites, is new. By now, everybody knows that tests are a good way to catch errors. By making testing a core practice and by emphasizing _automated_ tests, Extreme Programming has helped to make testing productive and fun, rather than a chore that programmers dislike. The Pharo community has a long tradition of testing because of the incremental style of development supported by its programming environment. In traditional Pharo development, a programmer writes tests in a playground as soon as a method was finished. Sometimes a test would be incorporated as a comment at the head of the method that it exercised, or tests that needed some set up would be included as example methods in the class. The problem with these practices is that tests in a playground are not available to other programmers who modify the code. Comments and example methods are better in this respect, but there is still no easy way to keep track of them and to run them automatically. Tests that are not run do not help you to find bugs! Moreover, an example method does not inform the reader of the expected result: you can run the example and see the \(perhaps surprising\) result, but you will not know if the observed behaviour is correct. Using a testing framework such as SUnit is valuable because it allows us to write tests that are self-checking: the test itself defines what the correct result should be. It also helps us \(1\) to organize tests into groups, \(2\) to describe the context in which the tests must run, and \(3\) to run a group of tests automatically. As you saw, in less than two minutes you can write tests using SUnit, so instead of writing small code snippets in a playgound, we encourage you to use SUnit and get all the advantages of stored and automatically executable tests. ### Why testing is important @sec:whytest Now that you see that writing tests is simple. Let's step back and analyze the situation. Unfortunately, many developers believe that tests are a waste of their time. After all, _they_ do not write bugs, only _other_ programmers do that. Most of us have said, at some time or other: _I would write tests if I had more +time._ If you never write a bug, and if your code will never be changed in the future, then indeed tests are a waste of your time. However, this most likely also means that your application is trivial, or that it is not used by you or anyone else. Think of tests as an investment for the future: having a test suite is quite useful now, but it will be _extremely_ useful when your application, or the environment in which it runs, changes in the future. Tests play several roles: - First, they provide documentation of the functionality that they cover. This documentation is active: watching the tests pass tells you that the documentation is up to date. - Second, tests help developers to confirm that some changes that they have just made to a package have not broken anything else in the system, and to find the parts that break when that confidence turns out to be misplaced. - Finally, writing tests during, or even before, programming forces you to think about the functionality that you want to design, _and how it should appear to the client code_, rather than about how to implement it. By writing the tests first, i.e., before the code, you are compelled to state the context in which your functionality will run, the way it will interact with the client code, and the expected results. Your code style will definitively improve. Several software development methodologies such as _eXtreme Programming_ and Test-Driven Development \(TDD\) advocate writing tests before writing code. This may seem to go against your deep instincts as software developers. All we can say is: go ahead and try it. Writing the tests before the code helps you know what we want to code, helps you know when you are done, and helps us conceptualize the functionality of a class and to design its interface. Moreover, test-first development gives you the courage to change our application, because you will know when you break something. We cannot test all aspects of any realistic application. Covering a complete application is simply impossible and is the goal of testing. Even with a good test suite some bugs will still creep into the application, where they can lay dormant waiting for an opportunity to damage your system. If you find that this has happened, take advantage of it! As soon as you uncover the bug, write a test that exposes it, run the test, and watch it fail. Now you can start to fix the bug: the test will tell you when you are done. ### What makes a good test? Writing good tests is a skill that you can learn by practicing. Let us look at the properties that tests should have to get the maximum benefit. - _Tests should be repeatable._ You should be able to run a test as often as you want, and always get the same answer. - _Tests should run without human intervention_. You should be able to run them unattended. - _Tests should tell a story._ Each test should cover one aspect of a piece of code. A test should act as a scenario that you or someone else can read to understand a piece of functionality. - _Tests should have a change frequency lower than that of the functionality they cover_. You do not want to have to change all your tests every time you modify your application. One way to achieve this is to write tests based on the public interfaces of the class that you are testing. It is OK to write a test for a private _helper_ method if you feel that the method is complicated enough to need the test, but you should be aware that such a test may have to be changed, or thrown away entirely, when you think of a better implementation. One consequence of such properties is that the number of tests should be somewhat proportional to the number of functions to be tested: changing one aspect of the system should not break all the tests but only a limited number. This is important because having 100 tests fail should send a much stronger message than having 10 tests fail. However, it is not always possible to achieve this ideal: in particular, if a change breaks the initialization of an object, or the set-up of a test, it is likely to cause all of the tests to fail. % !!!! right-Bicep. % Writing tests is not difficult in itself. Choosing ''what'' to test is much more % difficult. Some programmers use the "right-BICEP" principle. It stands for: % - Right: Are the results right? % - B: Are all the boundary conditions correct? % - I: Can you check inverse relationships? % - C: Can you cross-check results using other means? % - E: Can you force error conditions to happen? % - P: Are performance characteristics within bounds? Now let's go back and write a couple of tests using SUnit. ### SUnit by example We show a step by step example. We continue with the example that tests the class `Set`. Try editing and compiling the code as we go along. Pay attention: test classes are special classes. As subclasses of `TestCase` they have a different behavior that normal classes: their methods which start with `test` are automatically executed on newly created instances of the class. This is what happens when you press the icon close to the method in a class browser \(as shown in Figure *@fig:browsertests@*\). #### Step 1: Create the test class We use the class `MyExampleSetTest` to group all the tests related to the class `Set`. First you should create a new subclass of `TestCase` called `MyExampleSetTest`. ``` TestCase subclass: #MyExampleSetTest + instanceVariableNames: '' + classVariableNames: '' + package: 'MySetTest' ``` #### Step 2: Write a test method Let's create some tests by defining some methods in the class `MyExampleSetTest`. Each method represents one test. The names of the methods should start with the string `'test'` so that SUnit collects them into test suites. Test methods take no arguments. Define the following test method named `testIncludes`. It tests the `includes:` method of class `Set`. The test says that sending the message `includes: 5` to a set containing 5 should return `true`. ``` MyExampleSetTest >> testIncludes + | full | + full := Set with: 5 with: 6. + self assert: (full includes: 5). + self assert: (full includes: 6) ``` As you see this is quite simple. Let's continue. #### Step 3: Run the test The easiest way to run the tests is directly from the browser. Simply click on the icon of the class name, or on an individual test method, or use the _Run +tests \(t\)_ . The test methods will be flagged green or red, depending on whether they pass or not \(as shown in Figure *@fig:browsertests@*\). ![Running SUnit tests from the System Browser: Just click on the round little button close to the class or method.](figures/updatedbrowsertests.png width=80&label=fig:browsertests) #### Step 4: Write more tests Let's create more tests by defining some methods in the class `MyExampleSetTest`. The second test, named `testOccurrences`, verifies that the number of occurrences of 5 in `full` set is equal to one, even if we add another element 5 to the set. ``` MyExampleSetTest >> testOccurrences + | empty full | + empty := Set new. + full := Set with: 5 with: 6. + self assert: (empty occurrencesOf: 0) equals: 0. + self assert: (full occurrencesOf: 5) equals: 1. + full add: 5. + self assert: (full occurrencesOf: 5) equals: 1 ``` Finally, we test that the set no longer contains the element 5 after we have removed it. ``` MyExampleSetTest >> testRemove + | full | + full := Set with: 5 with: 6. + full remove: 5. + self assert: (full includes: 6). + self deny: (full includes: 5) ``` Note the use of the method `TestCase >> deny:` to assert something that should not be true. `aTest deny: anExpression` is equivalent to `aTest assert: anExpression not`, but is much more readable. #### Step 5: Run all the tests You can also select sets of test suites to run, and obtain a more detailed log of the results using the SUnit Test Runner, which you can open by selecting the menu `World > Test Runner`. The _Test Runner_, shown in Figure *@fig:test-runner@*, is designed to make it easy to execute groups of tests. The left-most pane lists all of the packages that contain test classes \(i.e., subclasses of `TestCase`\). When some of these packages are selected, the test classes that they contain appear in the pane to the right. Abstract classes are italicized, and the test class hierarchy is shown by indentation, so subclasses of `ClassTestCase` are indented more than subclasses of `TestCase`. `ClassTestCase` is a class offering utilities methods to compute test coverage. ![Running SUnit tests using the _TestRunner_.](figures/updatedtestRunner.png width=80&label=fig:test-runner) Open a Test Runner, select the package _MySetTest_, and click the `Run Selected` button. #### Step 6: Alternative ways to execute tests You can also run a single test \(and print the usual pass/fail result summary\) by executing a _Print it_ on the following code: `MyExampleSetTest run: #testRemove`. Some people include an executable comment in their test methods as in `testRemove` below. For example the contents of the comment `self run: #testRemove` can be executed: select the expression inside the comment \(but not the comment\) and bring the menu to do a _Do it_. It will execute the test. ``` MyExampleSetTest >> testRemove + "self run: #testRemove" + | empty full | + empty := Set new. + full := Set with: 5 with: 6. + full remove: 5. + self assert: (full includes: 6). + self deny: (full includes: 5) ``` #### Step 7: Looking at a bug Introduce a bug in `MyExampleSetTest >> testRemove` and run the tests again. For example, change `6` to `7`, as in: ``` MyExampleSetTest >> testRemove + | empty full | + empty := Set new. + full := Set with: 5 with: 6. + full remove: 5. + self assert: (full includes: 7). + self deny: (full includes: 5) ``` The tests that did not pass \(if any\) are listed in the right-hand panes of the _Test Runner_. If you want to debug one, to see why it failed, just click on the name. Alternatively, you can execute one of the following expressions: ``` (MyExampleSetTest selector: #testRemove) debug + +MyExampleSetTest debug: #testRemove ``` #### Step 8: Interpret the results The method `assert:` is defined in the class `TestAsserter`. This is a superclass of `TestCase` and therefore all other `TestCase` subclasses and is responsible for all kinds of test result assertions. The `assert:` method expects a boolean argument, usually the value of a tested expression. When the argument is true, the test passes; when the argument is false, the test fails. There are actually three possible outcomes of a test: _passing_, _failing_, and _raising an error_. - **Passing**. The outcome that we hope for is that all of the assertions in the test are true, in which case the test passes. In the test runner, when all of the tests pass, the bar at the top turns green. - **Failing**. The obvious way is that one of the assertions can be false, causing the test to _fail_. - **Error**. The other possibility is that some kind of error occurs during the execution of the test, such as a _message not understood_ error or an _index out of bounds_ error. If an error occurs, the assertions in the test method may not have been executed at all, so we can't say that the test has failed; nevertheless, something is clearly wrong! In the _test runner_, failing tests cause the bar at the top to turn yellow, and are listed in the middle pane on the right, whereas tests with errors cause the bar to turn red, and are listed in the bottom pane on the right. As an exercise, modify your tests to provoke both errors and failures. ### The SUnit cookbook This section will give you more details on how to use SUnit. If you have used another testing framework such as JUnit, much of this will be familiar, since all these frameworks have their roots in SUnit. Normally you will use SUnit's GUI to run tests, but there are situations where you may not want to use it. #### About assert:equals: Note that we either used both `assert: aBoolean` and `assert: expression equals: aValue`. The second one provides nicer feedback when the assertion fails. The two following lines are equals. ``` self assert: (empty occurrencesOf: 0) equals: 0. +self assert: (empty occurrencesOf: 0) = 0. ``` Using `assert:equals:` provides a better feedback when the test is failing because we said explicitly that the result should be 0. #### Other assertions In addition to `assert:` and `deny:`, there are several other methods that can be used to make assertions. First, `assert:description:` and `deny:description:` take a second argument which is a message string that describes the reason for the failure, if it is not obvious from the test itself. Next, SUnit provides two additional methods, `should:raise:` and `shouldnt:raise:` for testing exception propagation. For example, you would use `self should: aBlock raise: anException` to test that a particular exception is raised during the execution of `aBlock`. The method below illustrates the use of `should:raise:`. ``` MyExampleSetTest >> testIllegal + | empty | + self should: [ empty at: 5 ] raise: Error. + self should: [ empty at: 5 put: #zork ] raise: Error ``` Try running this test. Note that the first argument of the `should:` and `shouldnt:` methods is a block that contains the expression to be executed. #### Running a single test Normally, you will run your tests using the Test Runner or using your code browser. If you don't want to launch the Test Runner from the World menu, you can execute `TestRunner open`. You can also run a single test as follows: ``` MyExampleSetTest run: #testRemove +>>> 1 run, 1 passed, 0 failed, 0 errors ``` #### Running all the tests in a test class Any subclass of `TestCase` responds to the message `suite` and builds a test suite that contains all the methods whose names start with the string _test_. To run the tests in the suite, send it the message `run`. For example: ``` MyExampleSetTest suite run +>>> 5 run, 5 passed, 0 failed, 0 errors ``` #### Must I subclass TestCase? In JUnit you can build a TestSuite from an arbitrary class containing `test*` methods. In SUnit you can do the same but you will then have to create a suite by hand and your class will have to implement all the essential `TestCase` methods like `assert:`. We recommend, however, that you not try to do this. The framework is there: use it. ### Defining a fixture In the previous example, we defined the context in each test methods and it was a bit boring to duplicate all the logic in any tests. In fact SUnit proposes a solution to this. #### Step 1: Define the class and context We can define the context using the two instance variables `full` and `empty` that we will use to represent a full and an empty set. ``` TestCase subclass: #MyExampleSetTest + instanceVariableNames: 'full empty' + classVariableNames: '' + package: 'MySetTest' ``` #### Step 2: Setting a reusable context The method `TestCase >> setUp` defines the context in which each of the tests will run. The message `setUp` is sent before the execution of each test method defined in the test class. Define the `setUp` method as follows, to initialize the `empty` variable to refer to an empty set and the `full` variable to refer to a set containing two elements. ``` MyExampleSetTest >> setUp + empty := Set new. + full := Set with: 5 with: 6 ``` In testing jargon the context is called the _fixture_ for the test. #### Step 3: Write some test methods Now the previous tests methods are much more compact and contain less duplication. ``` MyExampleSetTest >> testIncludes + self assert: (full includes: 5). + self assert: (full includes: 6) ``` ``` MyExampleSetTest >> testOccurrences + self assert: (empty occurrencesOf: 0) equals: 0. + self assert: (full occurrencesOf: 5) equals: 1. + full add: 5. + self assert: (full occurrencesOf: 5) equals: 1 ``` ``` MyExampleSetTest >> testRemove + full remove: 5. + self assert: (full includes: 6). + self deny: (full includes: 5) ``` ### Chapter summary This chapter explained why tests are an important investment in the future of your code. We explained in a step-by-step fashion how to define a few tests for the class `Set`. - To maximize their potential, unit tests should be fast, repeatable, independent of any direct human interaction and cover a single unit of functionality. - Tests for a class called `MyClass` belong in a class named `MyClassTest`, which should be introduced as a subclass of `TestCase`. - Initialize your test data in a `setUp` method. - Each test method should start with the word _test_. - Use the `TestCase` methods `assert:`, `deny:` and others to make assertions. - Run tests! As exercise, turn the examples given in the first chapter into tests. \ No newline at end of file diff --git a/Chapters/TinyChat/TinyChat.md b/Chapters/TinyChat/TinyChat.md new file mode 100644 index 0000000..5eb3d05 --- /dev/null +++ b/Chapters/TinyChat/TinyChat.md @@ -0,0 +1,187 @@ +## TinyChat: a fun and small chat client/server Pharo allows the definition of a REST server in a couple of lines of code thanks to the Teapot package by zeroflag, which extends the superb HTTP client/server Zinc developed by BetaNine and was given to the community. The goal of this chapter is to make you develop, in five small classes, a client/server chat application with a graphical client. This little adventure will familiarize you with Pharo and show the ease with which Pharo lets you define a REST server. Developed in a couple of hours, TinyChat has been designed as a pedagogical application. At the end of the chapter, we propose a list of possible improvements. TinyChat has been developed by O. Auverlot and S. Ducasse with a lot of fun. ### Objectives and architecture We are going to build a chat server and one graphical client as shown in Figure *@tinychatclient@*. ![Chatting with TinyChat.](figures/tinychatclient.png width=80&label=tinychatclient) The communication between the client and the server will be based on HTTP and REST. In addition to the classes `TCServer` and `TinyChat` \(the client\), we will define three other classes: `TCMessage` which represents exchanged messages \(as a future exercise you could extend TinyChat to use more structured elements such as JSON or STON \(the Pharo object format\), `TCMessageQueue` which stores messages, and `TCConsole` the graphical interface. ### Loading Teapot Teapot is hosted on github at [https://github.com/zeroflag/Teapot](https://github.com/zeroflag/Teapot) Use the following script to load it. ``` Metacello new + baseline: 'Teapot'; + repository: 'github://zeroflag/teapot:master/source'; + load. ``` Now we are ready to start. ### Message representation A message is a really simple object with a text and sender identifier. #### Class TCMessage We define the class `TCMessage` in the package `TinyChat`. ``` Object subclass: #TCMessage + instanceVariableNames: 'sender text separator' + classVariableNames: '' + category: 'TinyChat' ``` The instance variables are as follows: - `sender`: the identifier of the sender, - `text`: the message text, and - `separator`: a character to separate the sender and the text. #### Accessor creation We create the following accessors: ``` TCMessage >> sender + ^ sender + +TCMessage >> sender: anObject + sender := anObject + +TCMessage >> text + ^ text + +TCMessage >> text: anObject + text := anObject ``` ### Instance initialisation Each time an instance is created, its `initialize` method is invoked. We redefine this method to set the separator value to the string `>`. ``` TCMessage >> initialize + super initialize. + separator := '>'. ``` Now we create a class method named `from:text:` to instantiate a message \(a class method is a method that will be executed on a class and not on an instance of this class\): ``` TCMessage class >> from: aSender text: aText + ^ self new sender: aSender; text: aText; yourself ``` The message `yourself` returns the message receiver: this way we ensure that the returned object is the new instance and not the value returned by the `text:` message. This definition is equivalent to the following: ``` TCMessage class >> from: aSender text: aText + | instance | + instance := self new. + instance sender: aSender; text: aText. + ^ instance ``` ### Converting a message object into a string We add the method `printOn:` to transform a message object into a character string. The model we use is sender-separator-text-crlf. Example: 'john>hello !!!'. The method `printOn:` is automatically invoked by the method `printString`. This method is invoked by tools such as the debugger or object inspector. ``` TCMessage >> printOn: aStream + + aStream + << self sender; << separator; + << self text; << String crlf ``` ### Building a message from a string We also define two methods to create a message object from a plain string of the form: `'olivier>tinychat is cool'`. First we create the method `fromString:` filling up the instance variables of an instance. It will be invoked like this: `TCMessage new fromString: 'olivier>tinychat is cool'`, then the class method `fromString:` which will first create the instance. ``` TCMessage >> fromString: aString + "Compose a message from a string of this form 'sender>message'." + | items | + items := aString substrings: separator. + self sender: items first. + self text: items second. ``` You can test the instance method with the following expression `TCMessage new fromString: 'olivier>tinychat is cool'`. ``` TCMessage class >> fromString: aString + ^ self new + fromString: aString; + yourself ``` When you execute the following expression `TCMessage fromString: 'olivier>tinychat is cool'` you should get a message. We are now ready to work on the server. ### Starting with the server For the server, we are going to define a class to manage a message queue. This is not really mandatory but it allows us to separate responsibilities. #### Storing messages Create the class `TCMessageQueue` in the package _TinyChat-Server_. ``` Object subclass: #TCMessageQueue + instanceVariableNames: 'messages' + classVariableNames: '' + category: 'TinyChat-server' ``` The `messages` instance variable is an ordered collection whose elements are instances `TCMessage`. An `OrderedCollection` is a collection which dynamically grows when elements are added to it. We add an instance initialize method so that each new instance gets a proper messages collection. ``` TCMessageQueue >> initialize + super initialize. + messages := OrderedCollection new. ``` #### Basic operations on message list We should be able to add, clear the list, and count the number of messages, so we define three methods: `add:`, `reset`, and `size`. ``` TCMessageQueue >> add: aMessage + messages add: aMessage + +TCMessageQueue >> reset + messages removeAll + +TCMessageQueue >> size + ^ messages size ``` #### List of messages from a position When a client asks the server about the list of the last exchanged messages, it mentions the index of the last message it knows. The server then answers the list of messages received since this index. ``` TCMessageQueue >> listFrom: aIndex + ^ (aIndex > 0 and: [ aIndex <= messages size]) + ifTrue: [ messages copyFrom: aIndex to: messages size ] + ifFalse: [ #() ] ``` #### Message formatting The server should be able to transfer a list of messages to its client given an index. We add the possibility to format a list of messages \(given an index\). We define the method `formattedMessagesFrom:` using the formatting of a single message as follows: ``` TCMessageQueue >> formattedMessagesFrom: aMessageNumber + + ^ String streamContents: [ :formattedMessagesStream | + (self listFrom: aMessageNumber) + do: [ :m | formattedMessagesStream << m printString ] + ] ``` Note how the `streamContents:` lets us manipulate a stream of characters. ### The Chat server The core of the server is based on the Teapot REST framework. It supports the sending and receiving of messages. In addition this server keeps a list of messages that it communicates to clients. #### TCServer class creation We create the class `TCServer` in the _TinyChat-Server_ package. ``` Object subclass: #TCServer + instanceVariableNames: 'teapotServer messagesQueue' + classVariableNames: '' + category: 'TinyChat-Server' ``` The instance variable `messagesQueue` represents the list of received and sent messages. We initialize it like this: ``` TCServer >> initialize + super initialize. + messagesQueue := TCMessageQueue new. ``` The instance variable `teapotServer` refers to an instance of the Teapot server that we will create using the method `initializePort:` ``` TCServer >> initializePort: anInteger + teapotServer := Teapot configure: { + #defaultOutput -> #text. + #port -> anInteger. + #debugMode -> true + }. + teapotServer start. ``` The HTTP routes are defined in the method `registerRoutes`. Three operations are defined: - GET `messages/count`: returns to the client the number of messages received by the server, - GET `messages/`: the server returns messages from an index, and - POST `/message/add`: the client sends a new message to the server. ``` TCServer >> registerRoutes + teapotServer + GET: '/messages/count' -> (Send message: #messageCount to: self); + GET: '/messages/' -> (Send message: #messagesFrom: to: self); + POST: '/messages/add' -> (Send message: #addMessage: to: self) ``` Here we express that the path `message/count` will execute the message `messageCount` on the server itself. The pattern `` indicates that the argument should be expressed as number and that it will be converted into an integer. Error handling is managed in the method `registerErrorHandlers`. Here we see how we can get an instance of the class `TeaResponse`. ``` TCServer >> registerErrorHandlers + teapotServer + exception: KeyNotFound -> (TeaResponse notFound body: 'No such message') ``` Starting the server is done in the class method `TCServer class>>startOn:` that gets the TCP port as argument. ``` TCServer class >> startOn: aPortNumber + ^self new + initializePort: aPortNumber; + registerRoutes; + registerErrorHandlers; + yourself ``` We should also offer the possibility to stop the server. The method `stop` stops the teapot server and empties the message list. ``` TCServer >> stop + teapotServer stop. + messagesQueue reset. ``` Since there is a chance that you may execute the expression `TCServer startOn:` multiple times, we define the class method `stopAll` which stops all the servers by iterating over all the instances of the class `TCServer`. The method `TCServer class>>stopAll` stops each server. ``` TCServer class >> stopAll + self allInstancesDo: #stop ``` ### Server logic Now we should define the logic of the server. We define a method `addMessage` that extracts the message from the request. It adds a newly created message \(instance of class `TCMessage`\) to the list of messages. ``` TCServer >> addMessage: aRequest + messagesQueue add: (TCMessage from: (aRequest at: #sender) text: (aRequest at: #text)). ``` The method `messageCount` gives the number of received messages. ``` TCServer >> messageCount + ^ messagesQueue size ``` The method `messageFrom:` gives the list of messages received by the server since a given index \(specified by the client\). The messages returned to the client are a string of characters. This is definitively a point to improve - using string is a poor choice here. ``` TCServer >> messagesFrom: request + ^ messagesQueue formattedMessagesFrom: (request at: #id) ``` Now the server is finished and we can test it. First let us begin by starting it: ``` TCServer startOn: 8181 ``` Now we can verify that it is running either with a web browser \(Figure *@running@*\), or with a Zinc expression as follows: ``` ZnClient new url: 'http://localhost:8181/messages/count' ; get ``` Shell lovers can also use the curl command: ``` curl http://localhost:8181/messages/count ``` ![Testing the server.](figures/running.png width=80&label=running) We can also add a message the following way: ``` ZnClient new + url: 'http://localhost:8181/messages/add'; + formAt: 'sender' put: 'olivier'; + formAt: 'text' put: 'Super cool ce tinychat' ; post ``` ### The client Now we can concentrate on the client part of TinyChat. We decomposed the client into two classes: - `TinyChat` is the class that defines the connection logic \(connection, send, and message reception\), - `TCConsole` is a class defining the user interface. The logic of the client is: - During client startup, it asks the server the index of the last received message, - Every two seconds, it requests from the server the messages exchanged since its last connection. To do so, it passes to the server the index of the last message it got. #### TinyChat class We now define the class `TinyChat` in the package `TinyChat-client`. ``` Object subclass: #TinyChat + instanceVariableNames: 'url login exit messages console lastMessageIndex' + classVariableNames: '' + category: 'TinyChat-client' ``` This class defines the following instance variables: - url that contains the server url, - login a string identifying the client, - messages is an ordered collection containing the messages read by the client, - lastMessageIndex is the index of the last message read by the client, - exit controls the connection. While exit is false, the client regularly connects to the server to get the unread messages - console refers to the graphical console that allows the user to enter and read messages. We initialize these variables in the following instance `initialize` method. ``` TinyChat >> initialize + super initialize. + exit := false. + lastMessageIndex := 0. + messages := OrderedCollection new. ``` #### HTTP commands Now, we define methods to communicate with the server. They are based on the HTTP protocol. Two methods will format the request. One, which does not take an argument, builds the requests `/messages/add` and `/messages/count`. The other has an argument used to get the message given a position. ``` TinyChat >> command: aPath + ^'{1}{2}' format: { url . aPath } + +TinyChat >> command: aPath argument: anArgument + ^'{1}{2}/{3}' format: { url . aPath . anArgument asString } ``` Now that we have these low-level operations we can define the three HTTP commands of the client as follows: ``` TinyChat >> cmdLastMessageID + ^ self command: '/messages/count' + +TinyChat >> cmdNewMessage + ^self command: '/messages/add' + +TinyChat >> cmdMessagesFromLastIndexToEnd + "Returns the server messages from my current last index to the last one on the server." + ^ self command: '/messages' argument: lastMessageIndex ``` Now we can create commands but we need to emit them. This is what we look at now. ### Client operations We need to send the commands to the server and to get back information from the server. We define two methods. The method `readLastMessageID` returns the index of the last message received from the server. ``` TinyChat >> readLastMessageID + | id | + id := (ZnClient new url: self cmdLastMessageID; get) asInteger. + id = 0 ifTrue: [ id := 1 ]. + ^ id ``` The method `readMissingMessages` adds the last messages received from the server to the list of messages known by the client. This method returns the number of received messages. ``` TinyChat >> readMissingMessages + "Gets the new messages that have been posted since the last request." + | response receivedMessages | + response := (ZnClient new url: self cmdMessagesFromLastIndexToEnd; get). + ^ response + ifNil: [ 0 ] + ifNotNil: [ + receivedMessages := response substrings: String crlf. + receivedMessages do: [ :msg | messages add: (TCMessage fromString: msg) ]. + receivedMessages size. + ]. ``` We are now ready to define the refresh behavior of the client via the method `refreshMessages`. It uses a light process to read the messages received from the server at a regular interval. The delay is set to 2 seconds. \(The message `fork` sent to a block \(a lexical closure in Pharo\) executes this block in a light process\). The logic of this method is to loop as long as the client does not specify to stop via the state of the `exit` variable. The expression `(Delay forSeconds: 2) wait` suspends the execution of the process in which it is executed for a given number of seconds. ``` TinyChat >> refreshMessages + [ + [ exit ] whileFalse: [ + (Delay forSeconds: 2) wait. + lastMessageIndex := lastMessageIndex + (self readMissingMessages). + console print: messages. + ] + ] fork ``` The method `sendNewMessage:` posts the message written by the client to the server. ``` TinyChat >> sendNewMessage: aMessage + ^ ZnClient new + url: self cmdNewMessage; + formAt: 'sender' put: (aMessage sender); + formAt: 'text' put: (aMessage text); + post ``` This method is used by the method `send:` that gets the text written by the user. The string is converted into an instance of `TCMessage`. The message is sent and the client updates the index of the last message it knows, then it prints the message in the graphical interface. ``` TinyChat >> send: aString + "When we send a message, we push it to the server and in addition we update the local list of posted messages." + + | msg | + msg := TCMessage from: login text: aString. + self sendNewMessage: msg. + lastMessageIndex := lastMessageIndex + (self readMissingMessages). + console print: messages. ``` We should also handle the server disconnection. We define the method `disconnect` that sends a message to the client indicating that it is disconnecting and also stops the connecting loop of the server by putting `exit` to true. ``` TinyChat >> disconnect + self sendNewMessage: (TCMessage from: login text: 'I exited from the chat room.'). + exit := true ``` ### Client connection parameters Since the client should contact the server on specific ports, we define a method to initialize the connection parameters. We define the class method `TinyChat class>>connect:port:login:` so that we can connect the following way to the server: `TinyChat connect: 'localhost' port: 8080 login: 'username'` ``` TinyChat class >> connect: aHost port: aPort login: aLogin + + ^ self new + host: aHost port: aPort login: aLogin; + start ``` `TinyChat class>>connect:port:login:` uses the method `host:port:login:`. This method just updates the `url` instance variable and sets the `login` as specified. ``` TinyChat >> host: aHost port: aPort login: aLogin + url := 'http://' , aHost , ':' , aPort asString. + login := aLogin ``` Finally we define a method `start`: which creates a graphical console \(that we will define later\), tells the server that there is a new client, and gets the last message received by the server. Note that a good evolution would be to decouple the model from its user interface by using notifications. ``` TinyChat >> start + console := TCConsole attach: self. + self sendNewMessage: (TCMessage from: login text: 'I joined the chat room'). + lastMessageIndex := self readLastMessageID. + self refreshMessages. ``` ### User interface The user interface is composed of a window with a list and an input field as shown in Figure *@tinychatclient@*. ``` ComposablePresenter subclass: #TCConsole + instanceVariableNames: 'chat list input' + classVariableNames: '' + category: 'TinyChat-client' ``` Note that the class `TCConsole` inherits from `ComposablePresenter`. This class is the root of the user interface logic classes. `TCConsole` defines the logic of the client interface \(i.e. what happens when we enter text in the input field...\). Based on the information given in this class, the Spec user interface builder automatically builds the visual representation. The `chat` instance variable is a reference to an instance of the client model `TinyChat` and requires a setter method \(`chat:`\). The `list` and `input` instance variables both require an accessor. This is required by the User Interface builder. ``` TCConsole >> input + ^ input + +TCConsole >> list + ^ list + +TCConsole >> chat: anObject + chat := anObject ``` We set the title of the window by defining the method `title`. ``` TCConsole >> title + ^ 'TinyChat' ``` Now we should specify the layout of the graphical elements that compose the client. To do so we define the class method `TCConsole class>>defaultSpec`. Here we need a column with a list and an input field placed right below. ``` TCConsole class >> defaultSpec + + + ^ SpecLayout composed + newColumn: [ :c | + c add: #list; add: #input height: 30 ]; yourself ``` We should now initialize the widgets that we will use. The method `initializeWidgets` specifies the nature and behavior of the graphical components. The message `acceptBlock:` defines the action to be executed then the text is entered in the input field. Here we send it to the chat model and empty it. ``` TCConsole >> initializeWidgets + + list := ListModel new. + input := TextInputFieldModel new + ghostText: 'Type your message here...'; + enabled: true; + acceptBlock: [ :string | + chat send: string. + input text: '' ]. + self focusOrder add: input. ``` The method `print` displays the messages received by the client and assigns them to the list contents. ``` TCConsole >> print: aCollectionOfMessages + list items: (aCollectionOfMessages collect: [ :m | m printString ]) ``` Note that this method is invoked by the method `refreshMessages` and that changing all the list elements when we add just one element is rather ugly but ok for now. Finally we need to define the class method `TCConsole class>>attach:` that gets the client model as argument. This method opens the graphical elements and puts in place a mechanism that will close the connection as soon as the client closes the window. ``` TCConsole class >> attach: aTinyChat + | window | + window := self new chat: aTinyChat. + window openWithSpec whenClosedDo: [ aTinyChat disconnect ]. + ^ window ``` ### Now chatting ![Server access.](figures/messages.png width=60&label=messages) Now you can chat with your server. The example resets the server and opens two clients. ``` | tco tcs | +TCServer stopAll. +TCServer startOn: 8080. +tco := TinyChat connect: 'localhost' port: 8080 login: 'olivier'. +tco send: 'hello'. +tcs := TinyChat connect: 'localhost' port: 8080 login: 'Stef'. +tcs send: 'salut olivier' ``` ### Conclusion and ideas for future extensions We show that creating a REST server is really simple with Teapot. TinyChat provides a fun context to explore programming in Pharo and we hope that you like it. We designed TinyChat so that it favors extensions and exploration. Here is a list of possible extensions. - Using JSON or STON to exchange information and not plain strings. - Making sure that the clients can handle a failure of the server. - Adding only the necessary messages to the list in the graphical client. - Managing concurrent access in the server message collection \(if the server should handle concurrent requests the current implementation is not correct\). - Managing connection errors. - Getting the list of connected users. - Editing the delay to check for new messages. There are probably more extensions and we hope that you will have fun exploring some. The code of the project is available at [http://www.smalltalkhub.com/#!/\~olivierauverlot/TinyChat](http://www.smalltalkhub.com/#!/~olivierauverlot/TinyChat). \ No newline at end of file diff --git a/Chapters/Visitor/Visitor.md b/Chapters/Visitor/Visitor.md new file mode 100644 index 0000000..909d483 --- /dev/null +++ b/Chapters/Visitor/Visitor.md @@ -0,0 +1,208 @@ +## A little expression interpreter @cha:expressions In this chapter you will build a small mathematical expression interpreter. For example you will be able to build an expression such as \(3 + 4\) * 5 and then ask the interpreter to compute its value. You will revisit tests, classes, messages, methods and inheritance. You will also see an example of expression trees similar to the ones that are used to manipulate programs. For example, compilers and code refactorings as offered in Pharo and many modern IDEs are doing such manipulation with trees representing code. In addition, in the volume two of this book, we will extend this example to present the Visitor Design Pattern. ### Starting with constant expression and a test We start with constant expression. A constant expression is an expression whose value is always the same, obviously. Let us start by defining a test case class as follows: ``` TestCase subclass: #EConstantTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` We decided to define one test case class per expression class and this even if at the beginning the classes will not contain many tests. It is easier to define new tests and navigate them. Let us write a first test making sure that when we get a value, sending it the `evaluate` message returns its value. ``` EConstantTest >> testEvaluate + self assert: (EConstant new value: 5) evaluate equals: 5 ``` When you compile such a test method, the system should prompt you to get a class `EConstant` defined. Let the system drive you. Since we need to store the value of a constant expression, let us add an instance variable `value` to the class definition. At the end you should have the following definition for the class `EConstant`. ``` Object subclass: #EConstant + instanceVariableNames: 'value' + classVariableNames: '' + package: 'Expressions' ``` We define the method `value:` to set the value of the instance variable `value`. It is simply a method taking one argument and storing it in the `value` instance variable. ``` EConstant >> value: anInteger + value := anInteger ``` You should define the method `evaluate`: it should return the value of the constant. ``` EConstant >> evaluate + ... Your code ... ``` Your test should pass. ### Negation Now we can start to work on expression negation. Let us write a test and for this define a new test case class named `ENegationTest`. ``` TestCase subclass: #ENegationTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` The test `testEvaluate` shows that a negation applies to an expression \(here a constant\) and when we evalute we get the negated value of the constant. ``` ENegationTest >> testEvaluate + self assert: (ENegation new expression: (EConstant new value: 5)) evaluate equals: -5 ``` Let us execute the test and let the system help us to define the class. A negation defines an instance variable to hold the expression that it negates. ``` Object subclass: #ENegation + instanceVariableNames: 'expression' + classVariableNames: '' + package: 'Expressions' ``` We define a setter method to be able to set the expression under negation. ``` ENegation >> expression: anExpression + expression := anExpression ``` Now the `evaluate` method should request the evaluation of the expression and negate it. To negate a number the Pharo library proposes the message `negated`. ``` ENegation >> evaluate + ... Your code ... ``` ![A flat collection of classes \(with a suspect duplication\).](figures/Expressions.png width=70&label=figExpression) Following the same principle, we will add expression addition and multiplication. Then we will make the system a bit more easy to manipulate and revisit its first design. ### Adding expression addition To be able to do more than constant and negation we will add two extra expressions: addition and multiplication and after we will discuss about our approach and see how we can improve it. To add an expression that supports addition, we start to define a test case class and a simple test. ``` TestCase subclass: #EAdditionTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` A simple test for addition is to make sure that we add correctly two constants. ``` EAdditionTest >> testEvaluate + | ep1 ep2 | + ep1 := (EConstant new value: 5). + ep2 := (EConstant new value: 3). + self assert: (EAddition new right: ep1; left: ep2) evaluate equals: 8 ``` You should define the class `EAddition`: it has two instance variables for the two subexpressions it adds. ``` EExpression subclass: #EAddition + instanceVariableNames: 'left right' + classVariableNames: '' + package: 'Expressions' ``` Define the two corresponding setter methods `right:` and `left:`. Now you can define the `evaluate` method for addition. ``` EAddition >> evaluate + ... Your code ... ``` To make sure that our implementation is correct we can also test that we can add negated expressions. It is always good to add tests that cover _different_ scenario. ``` EAdditionTest >> testEvaluateWithNegation + | ep1 ep2 | + ep1 := ENegation new expression: (EConstant new value: 5). + ep2 := (EConstant new value: 3). + self assert: (EAddition new right: ep1; left: ep2) evaluate equals: -2 ``` ### Multiplication We do the same for multiplication: create a test case class named `EMultiplicationTest`, a test, a new class `EMultiplication`, a couple of setter methods and finally a new `evaluate` method. Let us do it fast and without much comments. ``` TestCase subclass: #EMultiplicationTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` ``` EMultiplicationTest >> testEvaluate + | ep1 ep2 | + ep1 := (EConstant new value: 5). + ep2 := (EConstant new value: 3). + self assert: (EMultiplication new right: ep1; left: ep2) evaluate equals: 15 ``` ``` Object subclass: #EMultiplication + instanceVariableNames: 'left right' + classVariableNames: '' + package: 'Expressions' ``` ``` EMultiplication >> right: anExpression + right := anExpression ``` ``` EMultiplication >> left: anExpression + left := anExpression ``` ``` EMultiplication >> evaluate + ... Your code ... ``` ### Stepping back It is interesting to look at what we built so far. We have a group of classes whose instances can be combined to create complex expressions. Each expression is in fact a tree of subexpressions as shown in Figure *@fig:ExpressionTrees@*. The figure shows two main trees: one for the constant expression `5` and one for the expression `-5 + 3`. Note that the diagram represents the number 5 as an object because in Pharo even small integers are objects in the same way the instances of `EConstant` are objects. ![Expressions are composed of trees.](figures/ExpressionTrees.pdf width=60&label=fig:ExpressionTrees) #### Messages and methods The implementation of the `evaluate` message is worth discussing. What we see is that _different_ classes understand the same message but execute different methods as shown in Figure *@figExpressionEvaluate@*. !!important A message represents an intent: it represents _what_ should be done. A method represents a specification of _how_ something should be executed. What we see is that sending a message `evaluate` to an expression is making a choice among the different implementations of the message. This point is central to object-oriented programming. !!important Sending a message is making a choice among all the methods with the same name. #### About common superclass So far we did not see the need to have an inheritance hierarchy because there is not much to share or reuse. Now adding a common superclass would be useful to convey to the reader of the code or a future extender of the library that such concepts are related and are different variations of expression. ![Evaluation: one message and multiple method implementations.](figures/ExpressionsEvaluate.pdf width=80&label=figExpressionEvaluate) #### Design corner: About addition and multiplication model We could have just one class called for example BinaryOperation and it can have an operator and this operator will be either the addition or multiplication. This solution can work and as usual having a working program does not mean that its design is any good. In particular having a single class would force us to start to write conditional based on the operator as follows ``` BinaryExpression >> evaluate + operator = #+ + ifTrue: [ left evaluate + right evaluate ] + ifFalse: [ left evaluate * right evaluate] ``` There are ways in Pharo to make such code more compact but we do not want to use it at this stage. For the interested reader, look for the message `perform:` that can execute a method based on its name. This is annoying because the execution engine itself is made to select methods for us so we want to avoid to bypass it using explicit condition. In addition when we will add power, division, subtraction we will have to have more cases in our condition making the code less readable and more fragile. As we will see as a general message in this book, sending a message is making a choice between different implementations. Now to be able to choose we should have different implementations and this implies having different classes. !!important Classes represent choices whose methods can be selected during message passing. Having more little classes is better than few large ones. What we could do is to introduce a common superclass between `EAddition` and `EMultiplication` but keep the two subclasses. We will probably do it in the future ### Negated as a message Negating an expression is expressed in a verbose way. We have to create explicitly each time an instance of the class `ENegation` as shown in the following snippet. ``` ENegation new expression: (EConstant new value: 5) ``` We propose to define a message `negated` on the expressions themselves that will create such instance of `ENegation`. With this new message, the previous expression can be reduced too. ``` (EConstant new value: 5) negated ``` #### negated message for constants Let us write a test to make sure that we capture well what we want to get. ``` EConstantTest >> testNegated + self assert: (EConstant new value: 6) negated evaluate equals: -6 ``` And now we can simply implement it as follows: ``` EConstant >> negated + ^ ENegation new expression: self ``` #### negated message for negations ``` ENegationTest >> testNegationNegated + self assert: (EConstant new value: 6) negated negated evaluate equals: 6 ``` ``` ENegation >> negated + ^ ENegation new expression: self ``` This definition is not the best we can do since in general it is a bad practice to hardcode the class usage inside the class. A better definition would be ``` ENegation >> negated + ^ self class new expression: self ``` But for now we keep the first one for the sake of simplicity #### negated message for additions We proceed similarly for additions. ``` EEAdditionTest >> testNegated + | ep1 ep2 | + ep1 := EConstant new value: 5. + ep2 := EConstant new value: 3. + self assert: (EAddition new right: ep1; left: ep2) negated evaluate equals: -8 ``` ``` EAddition >> negated + Your code ``` #### negated message for multiplications We proceed similarly for multiplications. ``` EMultiplicationTest >> testEvaluateNegated + | ep1 ep2 | + ep1 := EConstant new value: 5. + ep2 := EConstant new value: 3. + self assert: (EMultiplication new right: ep1; left: ep2) negated evaluate equals: -15 ``` ``` EMultiplication >> negated + ... Your code ... ``` Now all your tests should pass. And it is a good moment to save your package. ### Annoying repetition Let us step back and look at what we have. We have a working situation but again object-oriented design is to bring the code to a better level. Similarly to the situation of the `evaluate` message and methods we see that the functionality of `negated` is distributed over different classes. Now what is annoying is that we repeat the exact _same_ code over and over and this is not good \(see Figure *@fig:ExpressionsNegatedRepeated@*\). This is not good because if tomorrow we want to change the behavior of negation we will have to change it four times while in fact one time should be enough. ![Code repetition is a bad smell.](figures/ExpressionsNegatedRepeated.pdf width=70&label=fig:ExpressionsNegatedRepeated) What are the solutions? - We could define another class `Negator` that would do the job and each current classes would delegate to it. But it does not really solve our problem since we will have to duplicate all the message sends to call `Negator` instances. - If we define the method `negated` in the superclass \(`Object`\) we only need one definition and it will work. Indeed, when we send the message `negated` to an instance of `EConstant` or `EAddition` the system will not find it locally but in the superclass `Object`. So no need to define it four times but only one in class `Object`. This solution is nice because it reduces the number of similar definitions of the method `negated` but it is not good because even if in Pharo we can add methods to the class `Object` this is not a good practice. `Object` is a class shared by the entire system so we should take care not to add behavior only making sense for a single application. - The solution is to introduce a new superclass between our classes and the class `Object`. It will have the same property than the solution with `Object` but without poluting it \(see Figure *@figExpressionHierar@*\). This is what we do in the next section. ### Introducing Expression class ![Introducing a common superclass.](figures/ExpressionsHierarchy.png width=70&label=figExpressionHierar) Let us introduce a new class to obtain the situation depicted by Figure *@figExpressionHierar@*. We can simply do it by adding a new class: ``` Object subclass: #EExpression + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` and changing all the previous definitions to inherit from `EExpression` instead of `Object`. For example the class `EConstant` is then defined as follows. ``` EExpression subclass: #EConstant + instanceVariableNames: 'value' + classVariableNames: '' + package: 'Expressions' ``` We can also use for the first transformation the class refactoring _Insert superclass_. Refactorings are code transformations that do not change the behavior of a program. You can find it under the refactorings list when you bring the menu on the classes. Now it is only useful for the first changes. Once the classes `EConstant`, `ENegation`, `EAddition`, and `EMultiplication` are subclasses of `EEXpression`, we should focus on the method `negated`. Now the method refactoring _Push up_ will really help us. - Select the method `negated` in one of the classes - Select the refactoring _Push up_ The system will define the method `negated` in the superclass \(`EExpression`\) and remove all the negated methods in the classes. Now we obtain the situation described in Figure *@figExpressionHierar@*. It is a good moment to run all your tests again. They should all pass. Now you could think that we can introduce a new class named ArithmeticExpression as a superclass of `EAddition` and `EMultiplication`. Indeed this is something that we could do to factor out common structure and behavior between the two classes. We will do it later because this is basically just a repetition of what we have done. ### Class creation messages Until now we always sent the message new to a class followed by a setter method as shown below. ``` EConstant new value: 5 ``` We would like to take the opportunity to show that we can define simple **class** methods to improve the class instance creation interface. In this example it is simple and the benefits are not that important but we think that this is a nice example. With this in mind the previous example can now be written as follows: ``` EConstant value: 5 ``` Notice the important difference that in the first case the message is sent to the newly created instance while in the second case it is sent to the class itself. To define a class method is the same as to define an instance method \(as we did until now\). The only difference is that using the code browser you should click on the classSide button to indicate that you are defining a method that should be executed in response to a message sent to a class itself. #### Better instance creation for constants Define the following method on the class `EConstant`. Notice the definition now use `EConstant class` and not just `EConstant` to stress that we are defining the class method. ``` EConstant class >> value: anInteger + ^ self new value: anInteger ``` Now define a new test to make sure that our method works correctly. ``` EConstantTest >> testCreationWithClassCreationMessage + self assert: (EConstant value: 5) evaluate equals: 5 ``` #### Better instance creation for negations We do the same for the class `ENegation`. ``` ENegation class >> expression: anExpression + ... Your code ... ``` We write of course a new test as follows: ``` ENegationTest >> testEvaluateWithClassCreationMessage + self assert: (ENegation expression: (EConstant value: 5)) evaluate equals: -5 ``` #### Better instance creation for additions For the addition we add a class method named `left:right:` taking two arguments ``` EAddition class >> left: anInteger right: anInteger2 + ^ self new left: anInteger ; right: anInteger2 ``` Of course, since we are test infested we add a new test. ``` EEAdditionTest >> testEvaluateWithClassCreationMessage + | ep1 ep2 | + ep1 := EConstant constant5. + ep2 := EConstant constant3. + self assert: (EAddition left: ep1 right: ep2) evaluate equals: 8 ``` #### Better instance creation for multiplications We let you do the same for the multiplication. ``` EMultiplication class >> left: anExp right: anExp2 + ... Your code ... ``` And another test to check that everything is ok. ``` EMultiplicationTest >> testEvaluateWithClassCreationMessage + | ep1 ep2 | + ep1 := EConstant new value: 5. + ep2 := EConstant new value: 3. + self assert: (EMultiplication new left: ep1; right: ep2) evaluate equals: 15 ``` Run your tests! They should all pass. ### Introducing examples as class messages As you saw when writing the tests, it is quite annoying to repeat all the time the expressions to get a given tree. This is especially the case in the tests related to addition and multiplication as the one below: ``` EEAdditionTest >> testNegated + | ep1 ep2 | + ep1 := EConstant new value: 5. + ep2 := EConstant new value: 3. + self assert: (EAddition new right: ep1; left: ep2) negated evaluate equals: -8 ``` One simple solution is to define some class method returning typical instances of their classes. To define a class method remember that you should click the class side button. ``` EConstant class >> constant5 + ^ self new value: 5 ``` ``` EConstant class >> constant3 + ^ self new value: 3 ``` This way we can define the test as follows: ``` EEAdditionTest >> testNegated + | ep1 ep2 | + ep1 := EConstant constant5. + ep2 := EConstant constant3. + self assert: (EAddition new right: ep1; left: ep2) negated evaluate equals: -8 ``` The tools in Pharo support such a practice. If we tag a class method with the special annotation `` the browser will show a little icon on the side and when we click on it, it will open an inspector on the new instance. ``` EConstant class >> constant3 + + ^ self new value: 3 ``` using the same idea we defined the following class methods to return some examples of our classes. ``` EAddition class >> fivePlusThree + + | ep1 ep2 | + ep1 := EConstant new value: 5. + ep2 := EConstant new value: 3. + ^ self new left: ep1 ; right: ep2 ``` ``` EMultiplication class >> fiveTimesThree + + | ep1 ep2 | + ep1 := EConstant constant5. + ep2 := EConstant constant3. + ^ EMultiplication new left: ep1 ; right: ep2 ``` What is nice with such examples is that - they help documenting the class by providing objects that we can directly use, - they support the creation of tests by providing objects that can serve as input for tests, - they simplify the writing of tests. So think to use them. ### Printing It is quite annoying that we cannot really see an expression when we inspect it. We would like to get something better than `'aEConstant'` and `'anEAddition'` when we debug our programs. To display such information the debugger and inspector send to the objects the message `printString` which by default just prefix the name of the class with 'an' or 'a'. Let us change this situation. For this, we will specialize the method `printOn: aStream`. The message `printOn:` is called on the object when a program or the system send to the object the message `printString`. From that perspective `printOn:` is a system customisation point that developers can take advantage to enhance their programming experience. Note that we do not redefine the method `printString` because it is more complex and `printString` is reused for all the objects in the system. We just have to implement the part that is specific to a given class. In object-oriented design jargon, `printString` is a template method in the sense that it sets up a context which is shared by other objects and it hosts hook methods which are program customisation points. `printOn:` is a hook method. The term hook comes from the fact that code of subclasses are invoked in the hook place \(see Figure *@fig:ExpressionsHierarchyPrintOn@*\). The default definition of the method `printOn:` as defined on the class `Object` is the following: it grabs the class name and checks if it starts with a vowel or not and write to the stream the 'a/an class'. This is why by default we got `'anEConstant'` when we printed a constant expression. ``` Object >> printOn: aStream + "Append to the argument, aStream, a sequence of characters that + identifies the receiver." + | title | + title := self class name. + aStream + nextPutAll: (title first isVowel ifTrue: ['an '] ifFalse: ['a ']); + nextPutAll: title ``` ![printOn: and printString a "hooks and template" in action.](figures/ExpressionsHierarchyPrintOn.pdf width=70&label=fig:ExpressionsHierarchyPrintOn) #### A word about streams A stream is basically a container for a sequence of objects. Once we get a stream we can either read from it or write to it. In our case we will write to the stream. Since the stream passed to printOn: is a stream expecting characters we will add characters or strings \(sequence of characters\) to it. We will use the messages: `nextPut: aCharacter` and `nextPutAll: aString`. They add to the stream the arguments at the next position and following. We will guide you and it is simple. You can find more information on the chapter about Stream in the book: Pharo by Example available at [http://books.pharo.org](http://books.pharo.org) #### Printing constant Let us start with a test. Here we check that a constant is printed as its value. ``` EConstantTest >> testPrinting + self assert: (EConstant value: 5) printString equals: '5' ``` The implementation is then simple. We just need to put the value converted as a string to the stream. ``` EConstant >> printOn: aStream + aStream nextPutAll: value printString ``` #### Printing negation For a negation we should first put a '-' and then recurvisely call the printing process on the negated expression. Remember that sending the message `printString` to an expression should return its string representation. At least until now it will work for constants. ``` (EConstant value: 6) printString +>>> '6' ``` Here is a possible definition ``` ENegation >> printOn: aStream + aStream nextPutAll: '- ' + aStream nextPutAll: expression printString ``` By the way since all the messages are sent to the same object, this method can be rewritten as: ``` ENegation >> printOn: aStream + aStream + nextPutAll: '- '; + nextPutAll: expression printString ``` We can also define it as follows: ``` ENegation >> printOn: aStream + aStream nextPutAll: '- '. + expression printOn: aStream ``` The difference between the first solution and the alternate implementation is the following: In the solution using `printString`, the system creates two streams: one for each invocation of the message `printString`. One for printing the expression and one for printing the negation. Once the first stream is used the message `printString` converts the stream contents into a string and this new string is put inside the second stream which at the end is converted again as a string. So the first solution is not really efficient. With the second solution, only one stream is created and each of the method just put the needed string elements inside. At the end of the process, the single `printString` message converts it into a string. #### Printing addition Now let us write yet another test for addition printing. ``` EAdditionTest >> testPrinting + self assert: (EAddition fivePlusThree) printString equals: '( 5 + 3 )'. + self assert: (EAddition fivePlusThree) negated printString equals: '- ( 5 + 3 )' ``` Printing an addition is: put an open parenthesis, print the left expression, put ' + ', print the right expression and put a closing parenthese in the stream. ``` EAddition >> printOn: aStream + ... Your code ... ``` #### Printing multiplication And now we do the same for multiplication. ``` EMultiplicationTest >> testPrinting + self assert: (EMultiplication fiveTimesThree) negated printString equals: '- ( 5 * 3 )' ``` ``` EMultiplication >> printOn: aStream + ... Your code ... ``` ### Revisiting negated message for Negation Now we can go back on negating an expression. Our implementation is not nice even if we can negate any expression and get the correct value. If you look at it carefully negating a negation could be better. Printing a negated negation illustrates well the problem: we get two minuses instead of none. ``` (EConstant value: 11) negated +>> '- 11' + +(EConstant value: 11) negated negated +>> '- - 11' ``` A solution could be to change the printOn: definition and to check if the expression that is negated is a negation and in such case to not emit the minus. Let us say it now, this solution is not nice because we do not want to write code that depends on explicitly checking if an object is of a given class. Remember we want to send message and let the object do some actions. A good solution is to _specialize_ the message `negated` so that when it is sent to a _negation_ it does not create a new negation that points to the receiver but instead returns the expression itself, otherwise the method implemented in `EExpression` will be executed. This way the trees created by a `negated` message can never have negated negation but the arithmetic values obtained are correct. Let us implement this solution, we just need to implement a different version of the method `negated` for `ENegation`. Let us write a test! Since evaluating a single expression or a double negated one gives the same results, we need to define a structural test. This is what we do with the expression `exp negated class = ENegation` below. ``` NegationTest >> testNegatedStructureIsCorrect + | exp | + exp := EConstant value: 11. + self assert: exp negated class = ENegation. + self assert: exp negated negated equals: exp. ``` Now you should be able to implement the `negated` message on `ENegation`. ``` ENegation >> negated + ... Your code ... ``` #### Understanding method override When we send a message to an object, the system looks for the corresponding method in the class of the receiver then if it is not defined there, the lookup continues in the superclass of the previous class. By adding a method in the class `ENegation`, we created the situation shown in Figure *@fig:ExpressionsHierarchyOptimized@*. We said that the message `negated` is overridden in `ENegation` because for instances of `ENegation` it hides the method defined in the superclass `EExpression`. It works the following: - When we send the message `negated` to a constant, the message is not found in the class `EConstant` and then it is looked up in the class `EExpression` and it is found there and applied to the receiver \(the instance of `EConstant`\). - When we send the message `negated` to a negation, the message is found in the class `ENegation` and executed on the negation expression. ![The message `negated` is overridden in the class `ENegation`.](figures/ExpressionsHierarchyOptimized.png width=70&label=fig:ExpressionsHierarchyOptimized) ### Introducing BinaryExpression class @secBinaryExpression Now we will take a moment to improve our first design. We will factor out the behavior of `EAddition` and `EMultiplication`. ``` EExpression subclass: #EBinaryExpression + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` ``` EBinaryExpression subclass: #EAddition + instanceVariableNames: 'left right' + classVariableNames: '' + package: 'Expressions' ``` ``` EBinaryExpression subclass: #EMultiplication + instanceVariableNames: 'left right' + classVariableNames: '' + package: 'Expressions' ``` Now we can use again a refactoring to pull up the instance variables `left` and `right`, as well as the methods `left:` and `right:`. Select the class `EMuplication`, bring the menu and select in the Refactoring menu the instance variables refactoring _Push Up_. Then select the instance variables. Now you should get the following class definitions, where the instance variables are defined in the new class and removed from the two subclasses. ``` EExpression subclass: #EBinaryExpression + instanceVariableNames: 'left right' + classVariableNames: '' + package: 'Expressions' ``` ``` EBinaryExpression subclass: #EAddition + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` ``` EBinaryExpression subclass: #EMultiplication + instanceVariableNames: '' + classVariableNames: '' + package: 'Expressions' ``` We should get a situation similar to the one of Figure *@figExpressionFactoredState@*. All your tests should still pass. ![Factoring instance variables.](figures/ExpressionsHierarchyStateFactored.png width=70&label=figExpressionFactoredState) Now we can move the same way the methods. Select the method `left:` and apply the refactoring _Pull Up Method_. Do the same for the method `right:`. #### Creating a template and hook method Now we can look at the methods `printOn:` of additions and multiplications. They are really similar: Just the operator is changing. Now we cannot simply copy one of the definitions because it will not work for the other. But what we can do is to apply the same design point that implemented for `printString` and `printOn:`: we can create a template and hooks that will be specialized in the subclasses. We will use the method `printOn:` as a template with a hook redefined in each subclass. Let define the method `printOn:` in `EBinaryExpression` and remove the other ones from the two classes `EAddition` and `EMultiplication`. ``` EBinaryExpression >> printOn: aStream + aStream nextPutAll: '( '. + left printOn: aStream. + aStream nextPutAll: ' + '. + right printOn: aStream. + aStream nextPutAll: ' )' ``` Then you can do it manually or use the _Extract Method_ Refactoring: This refactoring creates a new method from a part of an existing method and sends a message to the new created method: select the ' + ' inside the method pane and bring the menu and select the Extract Method refactoring, and when prompt give the name `operatorString`. Here is the result you should get: ``` EBinaryExpression >> printOn: aStream + aStream nextPutAll: '( '. + left printOn: aStream. + aStream nextPutAll: self operatorString. + right printOn: aStream. + aStream nextPutAll: ' )' ``` ``` EBinaryExpression >> operatorString + ^ ' + ' ``` Now we can just redefine this method in the `EMultiplication` class to return the adequate string. ``` EMultiplication >> operatorString + ^ ' * ' ``` ![Factoring instance variables and behavior.](figures/ExpressionsHierarchyStateBehaviorFactored.png width=70&label=figExpressionsHierarchyStateBehaviorFactored) ### What did we learn The introduction of the class `EBinaryExpression` is a rich experience in terms of lessons that we can learn. - Refactorings are more than simple code transformations. Usually refactorings pay attention that their application does not change the behavior of programs. As we saw refactorings are powerful operations that really help doing complex operations in a few action. - We saw that the introduction of a new superclass and moving instance variables or method to a superclass does not change the structure or behavior of the subclasses. This is because \(1\) for the state, the structure of an instance is based on the state of its class and all its superclasses, \(2\) the lookup starts in the class of the receiver and look in superclasses. - While the method `printOn:` is by itself a hook for the method `printString`, it can also play the role of a template method. The method `operatorString` reuses the context created by the `printOn:` method which acts as a template method. In fact each time we do a self send we create a hook method that subclasses can specialize. ### About hook methods When we introduced `EBinaryExpression` we defined the method `operatorString` as follows: ``` EBinaryExpression >> operatorString + ^ ' + ' ``` ``` EMultiplication >> operatorString + ^ ' * ' ``` And you may wonder if it was worth to create a new method in the superclass and so that such one subclass redefines it. #### Creating hooks is always good First creating a hook is also a good idea. Because you rarely know how your system will be extended in the future. On this little example, we suggest you to add raising to power, division and this can be done with one class and two methods per new operator. #### Avoid not documenting hooks Second we could have just defined one method `operatorString` in each subclass and no method in the superclass `EBinaryExpression`. It would have worked because `EBinaryExpression` is not meant to have direct instances. Therefore there is no risk that a `printOn:` message is sent to one of its instance and cause a error because no method `operatorString` is found. The code would have looked like the following: ``` EAddition >> operatorString + ^ ' + ' ``` ``` EMultiplication >> operatorString + ^ ' * ' ``` ![Better design: Declaring an abstract method as a way to document a hook method.](figures/ExpressionsHierarchyBinaryAbstract.pdf width=70&label=figExpressionsHierarchyBinaryAbstract) Now such design is not really good because as a potential extender of the code, developers will have to guess reading the subclass definitions that they should also define a method `operatorString`. A much better solution in that case is to define what we can an abstract method in the superclass as follows: ``` EBinaryExpression >> operatorString + ^ self subclassResponsibility ``` Using the message `subclassResponsibility` declares that a method is abstract and that subclasses should redefine it explicitly. Using such an approach we get the final situation represented in Figure *@figExpressionsHierarchyBinaryAbstract@*. In the solution presented before \(section *@secBinaryExpression@*\) we decided to go for the simplest solution and it was to use one of the default value \(' + '\) as a default definition for the hook in the superclass `EExpression`. It was not a good solution and we did it on purpose to be able to have this discussion. It was not a good solution since it was using a specific subclass. It is better to define a default value for a hook in the superclass when this default value makes sense in the class itself. Note that we could also define `evaluate` as an abstract method in `EExpression` to indicate clearly that each subclass should define an `evaluate`. ### Variables Up until now our mathematical expressions are rather limited. We only manipulate constant-based expressions. What we would like is to be able to manipulate variables too. Here is a simple test to show what we mean: we define a variable named `'x'` and then we can later specify that `'x'` should take a given value. Let us create a new test class named `EVariableTest` and define a first test `testValueOfx`. ``` EVariableTest >> testValueOfx + self assert: ((EVariable new id: #x) evaluateWith: {#x -> 10} asDictionary) equals: 10. ``` #### Some technical points Let us explain a bit what we are doing with the expression `{#x -> 10} asDictionary`. We should be able to specify that a given variable name is associated with a given value. For this we create a dictionary: a dictionary is a data structure for storing keys and their associated value. Here a key is the variable and the value its associated value. Let us present some details first. ##### Dictionaries A dictionary is a data structure containing pairs \(key value\) and we can access the value of a given key. It can use any object as key and any object as values. Here we simply use a symbol `#x` since symbols are unique within the system and as such we are sure that we cannot have two keys looking the same but having different values. ``` | d | +d := Dictionary new + at: #x put: 33; + at: #y put: 52; + at: #z put: 98. +d at: y +>>> 52 ``` The previous dictionary can be easily expressed more compactly using `{#x -> 33 . #y -> 52 . #z -> 98} asDictionary`. ``` {#x -> 33 . #y -> 52 . #z -> 98} asDictionary at: #y +>>> 52 ``` ##### Dynamic Arrays The expression `{ }` creates a dynamic array. Dynamic arrays executes their expressions and store the resulting values. ``` {2 + 3 . 6 - 2 . 7-2 } +>>> ==#(5 4 5)== ``` ##### Pairs The expression `#x -> 10` creates a pair with a key and a value. ``` | p | +p := #x -> 10. +p key +>>> #x +p value +>>> 10 ``` #### Back to variable expressions If we go a step further, we want to be able to build more complex expressions where instead of having constants we can manipulate variables. This way we will be able to build more advanced behavior such as expression derivations. ``` EExpression subclass: #EVariable + instanceVariableNames: 'id' + classVariableNames: '' + package: 'Expressions' ``` ``` EVariable >> id: aSymbol + id := aSymbol ``` ``` EVariable >> printOn: aStream + aStream nexPutAll: id asString ``` What we see is that we need to be able to pass bindings \(a binding is a pair key, value\) when evaluating a variable. The value of a variable is the value of the binding whose key is the name of the variable. ``` EVariable >> evaluateWith: aBindingDictionary + ^ aBindingDictionary at: id ``` Your tests should all pass at this point. For more complex expressions \(the ones that interest us\) here are two tests. ``` EVariableTest >> testValueOfxInNegation + self assert: ((EVariable new id: #x) negated + evaluateWith: {#x -> 10} asDictionary) equals: -10 ``` What the second test shows is that we can have an expression and given a different set of bindings the value of the expression will differ. ``` EVariableTest >> testEvaluateXplusY + | ep1 ep2 add | + ep1 := EVariable new id: #x. + ep2 := EVariable new id: #y. + add := EAddition left: ep1 right: ep2. + + self assert: (add evaluateWith: { #x -> 10 . #y -> 2 } asDictionary) equals: 12. + self assert: (add evaluateWith: { #x -> 10 . #y -> 12 } asDictionary) equals: 22 ``` #### Non working approaches A non working solution would be to add the following method to `EExpression` ``` EEXpression >> evaluateWith: aDictionary + ^ self evaluate ``` However it does not work for at least the following reasons: - It does not use its argument. It only works for trees composed out exclusively of constant. - When we send a message `evaluateWith:` to an addition, this message is then turned into an `evaluate` message sent to its subexpression and such subexpression do not get an `evaluateWith:` message but an `evaluate`. Alternatively we could add the binding to the variable itself and only provide an `evaluate` message as follows: ``` (EVariable new id: #x) bindings: { #x -> 10 . #y -> 2 } asDictionary ``` But it fully defeats the purpose of what a variable is. We should be able to give different values to a variable embedded inside a complex expression. #### The solution: adding evaluateWith: We should transform all the implementations and message sends from `evaluate` to `evaluateWith:` Since this is a tedious task we will use the method refactoring _Add Parameter_. Since a refactoring applies itself on the complete system, we should be a bit cautious because other Pharo classes implement methods named `evaluate` and we do not want to impact them. So here are the steps that we should follow. - Select the Expression package - Choose Browse Scoped \(it brings a browser with only your package\) - Using this browser, select a method evaluate - Select the _Add Parameter_ refactoring: type `evaluateWith:` as method selector and proceed when prompted for a default value `Dictionary new`. This last expression is needed because the engine will rewrite all the messages `evaluate` but `evaluateWith: Dictionary new`. - The system is performing many changes. Check that they only touch your classes and accept them all. A test like the following one: ``` EConstant >> testEvaluate + self assert: (EConstant constant5) evaluate equals: 5 ``` is transformed as follows: ``` EConstant >> testEvaluate + self assert: ((EConstant constant5) evaluateWith: Dictionary new) equals: 5 ``` Your tests should nearly all pass except the ones on variables. Why do they fail? Because the refactoring transformed message sends `evaluate` but `evaluateWith: Dictionary new` and this even in methods `evaluate`. ``` EAddition >> evaluateWith: anObject + ^ (right evaluateWith: Dictionary new) + (left evaluateWith: Dictionary new) ``` This method should be transformed as follows: We should pass the binding to the argument of the `evaluateWith:` recursive calls. ``` EAddition >> evaluateWith: anObject + ^ (right evaluateWith: anObject) + (left evaluateWith: anObject) ``` Do the same for the multiplications. ``` ENegation >> evaluateWith: anObject + ^ (expression evaluateWith: anObject) negated ``` ### Conclusion This little exercise was full of learning potential. Here is a little summary of what we explained and we hope you understood. - A message specifies an intent while a method is a named list of execution. We often have one message and a list of methods with the same name. - Sending a message is finding the method corresponding to the message selector: this selection is based on the class of the object receiving the message. When we look for a method we start in the class of the receiver and go up the inheritance link. - Tests are a really nice way to specify what we want to achieve and then to verify after each change that we did not break something. Tests do not prevent bugs but they help us building confidence in the changes we do by identifying fast errors. - Refactorings are more than simple code transformations. Usually refactorings pay attention their application does not change the behavior of program. As we saw refactorings are powerful operations that really help doing complex operation in a few action. - We saw that the introduction of a new superclass and moving instance variables or method to a superclass does not change the structure or behavior of the subclasses. This is because \(1\) for the state, the structure of an instance is based on the state of its class and all its superclasses, \(2\) the lookup starts in the class of the receiver and look in superclasses. - Each time we send a message, we create a potential place \(a hook\) for subclasses to get their code definition used in place of the superclass's one. \ No newline at end of file diff --git a/Chapters/Visitor/VisitorSolution.md b/Chapters/Visitor/VisitorSolution.md new file mode 100644 index 0000000..e98d28f --- /dev/null +++ b/Chapters/Visitor/VisitorSolution.md @@ -0,0 +1,21 @@ +## Expressions solutions @cha:expressionssolutions Here are the possible solutions of the implementation we asked for the Expression Chapter *@cha:expressions@*. ### Evaluate message ``` EConstant >> evaluate + ^ value ``` ``` ENegation >> evaluate + ^ expression evaluate negated ``` ``` EAddition >> evaluate + ^ left evaluate + right evaluate ``` ``` EMultiplication >> evaluate + ^ left evaluate + right evaluate ``` ### Negated message ``` EAddition >>> negated + ^ ENegation new expression: self ``` ``` EMultiplication >>> negated + ^ ENegation new expression: self ``` ### Better class instance creation interface ``` ENegation class >> expression: anExpression + ^ self new expression: anExpression ``` ``` EMultiplication class >> left: anExp right: anExp2 + ^ self new left: anExp ; right: anExp2 ``` ### Printing addition and multiplication ``` EAddition >> printOn: aStream + aStream nextPutAll: '( '. + left printOn: aStream. + aStream nextPutAll: ' + '. + right printOn: aStream. + aStream nextPutAll: ' )' ``` ``` Emultiplication >> printOn: aStream + aStream nextPutAll: '( '. + left printOn: aStream. + aStream nextPutAll: ' * '. + right printOn: aStream. + aStream nextPutAll: ' )' ``` ### Negated negation ``` ENegation >> negated + ^ expression ``` ### evaluateWith: ``` EMultiplication >> evaluateWith: anObject + ^ (right evaluateWith: anObject) + (left evaluateWith: anObject) ``` \ No newline at end of file diff --git a/Chapters/Wallet/Wallet.md b/Chapters/Wallet/Wallet.md new file mode 100644 index 0000000..a5dee87 --- /dev/null +++ b/Chapters/Wallet/Wallet.md @@ -0,0 +1,197 @@ +## An electronic wallet @cha:wallet In this chapter you will develop a wallet. You will start by designing tests to define the behavior of our program, then we will define the methods according. Pay attention we will not give you all the solutions and the code. ### A first test Since we want to know if the code we will develop effectively does what it should do, we will write tests. A test can be as simple as verifying if our wallet contains money. To test that a newly created wallet does not contain money we can write a test as follow: ``` | w | +w := Wallet new. +w money = 0. ``` However doing it is tedious because we would have to manually run all the tests . We will use SUnit a system that automatically runs tests once we define them. Our process will be the following one: - imagine what we want to define - define a test method - execute it and check that it is failing - define the method and fix it until the test pass. With SUnit, tests are defined as methods inside a class subclass from `TestCase`. So let us start to define a test class named `WalletTest` inside the package `Wallet`. ``` TestCase subclass: #WalletTest + instanceVariableNames: '' + classVariableNames: '' + package: 'Wallet' ``` And now we can define a test. To define a test, we define a method starting with `test`. Here is the definition of the same test as before but using SUnit. ``` WalletTest >> testWalletAtCreationIsZero + | w | + w := Wallet new. + self assert: w money = 0 ``` Now executing a test can be done in different ways: - click on the icon close to the method in class browser, - use the TestRunner tools, - execute `WalletTest debug: #testWalletAtCreationIsZero` or `WalletTest run: #testWalletAtCreationIsZero` Now you should get started. Define the class `Wallet` inside the package `Wallet`. ``` Object subclass: #Wallet + instanceVariableNames: '' + classVariableNames: '' + package: 'Wallet' ``` Run the test! It should be red and now define the method `money`. For now this method is plain stupid and will return 0. In the following of course it will sum all the coins and return such sum. ``` Wallet >> money + ^ 0 ``` ### Adding coins Now we should be able to add coins to a wallet. Let us first define a new test `testCoins`. ``` WalletTest >> testCoins + | w | + w := Wallet new. + w add: 2 coinsOfValue: 0.50. + w add: 3 coinsOfValue: 0.20. + self assert: w coinNumber = 5 ``` The test creates a wallet and uses the method `add:coinsOfValue:` to add several coins of different values. Here we add 2 coins of 0.50 cents and 3 of 0.20 cents. Finally the test verifies that we did not lose any coins: i.e., that we have effectively 5 coins in our wallet. Doing so the test specifies the behavior of the method `add:coinsOfValue:` that you will have to define later. Now we should think how we will represent our wallet. We need to count how many coins of a given values are added or removed to a wallet. If we use an array or an ordered collection, we will have to maintain a mapping between the index and its corresponding value. Using a set will not really work since we will lose the occurrence of each coins. ### Looking at Bag A good structure to represent a wallet is a bag, instance of the class `Bag`: a bag keeps elements and their respective occurrences. Let us have a look at a bag example before continuing. You can add and remove elements of a bag and iterate on them. Let us play with it. First we create a bag and we expect it to be empty: ``` | aFruitBag | +aFruitBag := Bag new. +aFruitBag size. +>>> 0 ``` Then we add 3 bananas and verify that our bag really contains the three bananas we just added: ``` aFruitBag add: #Banana withOccurrences: 3. +aFruitBag size. +>>> 3 ``` Now let us add different fruits: ``` aFruitBag add: #Apple withOccurrences: 10. +aFruitBag size. +>>> 13 ``` Now we check that they are not mixed together. ``` aFruitBag occurrencesOf: #Apple. +>>> 10 ``` We can also add a single fruit to our bag. ``` aFruitBag add: #Banana. +aFruitBag occurrencesOf: #Banana. +>>> 4 ``` We can then iterate over all the contents of the bag using the message `do:`. The code snippet will print on the Transcript \(open>Tools>Transcript\) all the elements one by one. ``` 7 timesRepeat: [aFruitBag remove: #Apple]. +aFruitBag do: [ :each | each logCr ]. ``` ``` #Banana +#Banana +#Banana +#Banana +#Apple +#Apple +#Apple ``` Since for an element we know its occurrence we can iterate differently as follows: ``` aFruitBag doWithOccurrences: [ :each :occurrence | ('There is ' , occurrence printString , ' ', each ) logCr ] ``` We get the following trace in the Transcript. ``` 'There is 4 Banana' +'There is 10 Apple' ``` We could change a bit the code to print correctly 'There is' and 'There are' depending on the occurrence. We left this as an exercise for you. ### Using a bag for a wallet Since we can know how many coins of a given value are in a bag, a bag is definitively a good structure for our wallet. We will define add an instance variable `bagCoins` to the class and the methods - `add: anInteger coinsOfValue: aCoinNumber`, - `initialize`, and - `coinsOfValue:`. Let us start with the method `initialize`. We define the method `initialize` as follows. It is invoked automatically when an instance is created. ``` Wallet >> initialize + bagCoins := Bag new ``` Now define the method `add: anInteger coinsOfValue: aNumber`. Browse the class `Bag` to find the messages that you can send to a bag. ``` Wallet >> add: anInteger coinsOfValue: aNumber + "Add to the receiver, anInteger times a coin of value aNumber" + + ... Your solution ... ``` We can define the method `coinsOfValue:` that returns the number of coins of a given value \(looks like the same as asking how many bananas are in the fruit bag\). ``` Wallet >> coinsOfValue: aNumber + + ^ ... Your solution ... ``` ### More tests The previous test is limited in the sense that we cannot distinguish if the coins are not mixed. It would be bad that a wallet would convert cents into euros. So let us define a new test to verify that the added coins are not mixed. ``` WalletTest >> testCoinsAddition + | w | + w := Wallet new. + w add: 2 coinsOfValue: 0.50. + w add: 3 coinsOfValue: 0.20. + self assert: (w coinsOfValue: 0.5) = 2 ``` We should also test that when we add twice the same coins they are effectively added. ``` WalletTest >> testCoinsAdditionISWorking + | w | + w := Wallet new. + w add: 2 coinsOfValue: 0.50. + w add: 6 coinsOfValue: 0.50. + self assert: (w coinsOfValue: 0.5) = 8 ``` ### Testing money Now we can test that the `money` message returns the amount of money contained in the wallet and we should change the implementation of the `money`. We define two tests. ``` WalletTest >> testMoney + | w | + w := Wallet new. + w add: 2 coinsOfValue: 0.50. + w add: 3 coinsOfValue: 0.20. + w add: 1 coinsOfValue: 0.02. + self assert: w money = 1.62 ``` ``` WalletTest >> testMoney2 + | w | + w := Wallet new. + w add: 2 coinsOfValue: 0.50. + w add: 3 coinsOfValue: 0.20. + w add: 1 coinsOfValue: 0.02. + w add: 5 coinsOfValue: 0.05. + self assert: w money = 1.87 ``` Now we should implement the method `money`. ``` Wallet >> money + + ^ ... Your solution ... ``` ### Checking to pay an amount Now we can add a new message to know whether we can pay a certain amount. But let us write some tests first. ``` WalletTest >> testCanPay + | w | + w := Wallet new. + w add: 2 coinsOfValue: 0.50. + w add: 3 coinsOfValue: 0.20. + w add: 1 coinsOfValue: 0.02. + w add: 5 coinsOfValue: 0.05. + self assert: (w canPay: 2) not. + self assert: (w canPay: 0.50). ``` Define the message `canPay:`. ``` Wallet >> canPay: amountOfMoney + "returns true when we can pay the amount of money" + + ^ ... Your solution ... ``` ### Biggest coin Now let us define a method to determine the largest coin in a wallet. We write a test. ``` WalletTest >> testBiggestCoins + | w | + w := Wallet new. + w add: 10 coinsOfValue: 0.50. + w add: 10 coinsOfValue: 0.20. + w add: 10 coinsOfValue: 0.10. + self assert: w biggest equals: 0.50. ``` Note that the `assert:` message can also be replaced `assert:equals:` and this is what we did: we replaced the expression `self assert: w biggest = 0.5` by `self assert: w biggest equals: 0.50`. Now we should define the method `biggest`. ``` Wallet >> biggest + "Returns the biggest coin. For example, {(3 -> 0.5) . (3 -> 0.2) . (5-> 0.1)} biggest -> 0.5" + + ^ ... Your solution ... ``` ### Biggest below a value We can also define the method `biggestBelow:` that returns the first coin whose value is strictly smaller than the argument. `{(3 -> 0.5) . (3 -> 0.2) . (5-> 0.1)} biggestBelow: 0.40` returns 0.2. ``` WalletTest >> testBiggestCoinsBelow + | w | + w := Wallet new. + w add: 10 coinsOfValue: 0.50. + w add: 10 coinsOfValue: 0.20. + w add: 10 coinsOfValue: 0.10. + self assert: (w biggestBelow: 1) equals: 0.50. + self assert: (w biggestBelow: 0.5) equals: 0.20. + self assert: (w biggestBelow: 0.48) equals: 0.20. + self assert: (w biggestBelow: 0.20) equals: 0.10. + self assert: (w biggestBelow: 0.10) equals: 0. ``` ``` Wallet >> biggestBelow: anAmount + "Returns the biggest coin with a value below anAmount. For example, {(3 -> 0.5) . (3 -> 0.2) . (5-> 0.1)} biggestBelow: 0.40 -> 0.2" + + ^ ... Your solution ... ``` ### Improving the API #### Better string representation Now it is time to improve the API for our objects. First we should improve the way the wallet objects are printed so that we can debug more easily. For that we add the method `printOn: aStream` as follows: ``` Wallet >> printOn: aStream + super printOn: aStream. + aStream nextPutAll: ' (', self money asString, ')' ``` #### Easier addition We can improve the API to add coins in particular when we add only one coin. So now you start to get used to it. We define a test. ``` WalletTest >> testAddOneCoin + | w | + w := Wallet new. + w addCoin: 0.50. + self assert: (w coinsOfValue: 0.5) = 1. + self assert: w money equals: 0.5 ``` Define the method `addCoin:`. ``` Wallet >> addCoin: aNumber + "Add to the receiver a coin of value aNumber" + + ... Your solution ... ``` #### Removing coins We can now implement the removal of a coin. ``` WalletTest >> testRemove + | w | + w := Wallet new. + w add: 2 coinsOfValue: 0.50. + w add: 3 coinsOfValue: 0.20. + w add: 1 coinsOfValue: 0.02. + w add: 5 coinsOfValue: 0.05. + w removeCoin: 0.5. + self assert: w money = 1.37 ``` Define the method `removeCoin:`. ``` Wallet >> removeCoin: aCoinNumber + "Remove from the receiver a coin of value aNumber" + + ... Your solution ... ``` We can generalize this behavior with a method `remove:coinsOfValue:`. Write a test. ``` WalletTest >> testRemoveCoins + | w | + w := Wallet new. + + ... Your solution ... ``` ``` Wallet >> remove: anInteger coinsOfValue: aCoin + "Remove from the receiver, anInteger times a coin of value aNumber" + + bagCoins add: aCoin withOccurrences: anInteger ``` We can also define the method `biggestAndRemove` which removes the biggest coin and returns it. ``` Wallet >> biggestAndRemove + | b | + b := self biggest. + self removeCoin: b. + ^ b ``` ### Coins for paying: First version Now we would like to know the coins that we can use to pay a certain amount. We can define a method `coinsFor:` that will return a new wallet containing the coins to pay a given amount. This is a more challenging task and we will propose a first version then we will add more complex situations and propose a more complex solution. So let us define a test. ``` WalletTest >> testCoinsForPaying + + | w paid | + w := Wallet new. + w add: 10 coinsOfValue: 0.50. + w add: 10 coinsOfValue: 0.20. + w add: 10 coinsOfValue: 0.10. + paid := (w coinsFor: 2.5). + self assert: paid money equals: 2.5. + self assert: (paid coinsOfValue: 0.5) equals: 5 ``` ``` Wallet >> coinsFor: aValue + "Returns a wallet with the largest coins to pay a certain amount and an empty wallet if this is not possible" + + | res | + res := self class new. + ^ (self canPay: aValue) + ifFalse: [ res ] + ifTrue: [ self coinsFor: aValue into: res ] ``` The method `coinsFor:` creates wallet and fill with the largest coins comprising a given value. Using the previously defined methods, define a first version of the method `coinsFor: aValue into: accuWallet`. ``` Wallet >> coinsFor: aValue into: accuWallet + + ... Your solution ... ``` Here is a possible simple solution: we remove from the wallet the largest coin and we add it to the resulting wallet. This solution is not working well as we will show it. ``` Wallet >> coinsFor: aValue into: accuWallet + + [ accuWallet money < self money ] + whileTrue: [ accuWallet addCoin: self biggestAndRemove ]. + ^ accuWallet ``` ### Better heuristics Let us try some tests to see if our previous way to get coins is working. \(The previous algorithm does not work with such behavior.\) The first test checks that when there is no more coins of the biggest value, we check that the next coin is then used. ``` WalletTest >> testCoinsForPayingWithOtherCoins + | w paid | + w := Wallet new. + w add: 1 coinsOfValue: 0.50. + w add: 10 coinsOfValue: 0.20. + w add: 10 coinsOfValue: 0.10. + paid := (w coinsFor: 2.4). + self assert: paid money equals: 2.4. + self assert: (paid coinsOfValue: 0.5) equals: 1. + self assert: (paid coinsOfValue: 0.2) equals: 9. ``` Run the tests and define the method `coinsFor:` to invoke a copy of the method `coinsFor: aValue into: accuWallet` renamed `coinsFor: aValue into2: accuWallet` to start with. ``` Wallet >> coinsFor: aValue + "Returns a wallet with the largest coins to pay a certain amount and an empty wallet if this is not possible" + | res | + res := self class new. + ^ (self canPay: aValue) + ifFalse: [ res ] + ifTrue: [ self coinsFor: aValue into2: res ] ``` The previous algorithm \(implemented above in `coinsFor: aValue into:`\) does not work with such behavior. So you should start to address the problem and add more and more tests. The second test checks that even if there is a coin with a largest value, the algorithm selects the next one. Here to pay 0.6, we should get 0.5 then we should not take 0.2 the next coin but 0.1 instead. ``` WalletTest >> testCoinsForPayingWithOtherThanTop + | w paid | + w := Wallet new. + w add: 1 coinsOfValue: 0.50. + w add: 10 coinsOfValue: 0.20. + w add: 10 coinsOfValue: 0.10. + paid := (w coinsFor: 0.6). + self assert: paid money equals: 0.6. + self assert: (paid coinsOfValue: 0.5) equals: 1. + self assert: (paid coinsOfValue: 0.1) equals: 1. ``` In this version we check that the algorithm should skip multiple coins that are available. In the example, for 0.6 it should select: 0.5 then skip the remaining 0.5, and 0.2 to get one 0.1. ``` WalletTest >> testCoinsForPayingWithOtherThanTopMoreDifficult + | w paid | + w := Wallet new. + w add: 2 coinsOfValue: 0.50. + w add: 10 coinsOfValue: 0.20. + w add: 10 coinsOfValue: 0.10. + paid := (w coinsFor: 0.6). + self assert: paid money equals: 0.6. + self assert: (paid coinsOfValue: 0.5) equals: 1. + self assert: (paid coinsOfValue: 0.1) equals: 1. ``` The following one is a variant of the previous test where the biggest coin should be skipped. ``` WalletTest >> testCoinsForPayingWithOtherThanTopMoreDifficult2 + | w paid | + w := Wallet new. + w add: 1 coinsOfValue: 1. + w add: 2 coinsOfValue: 0.50. + w add: 10 coinsOfValue: 0.20. + w add: 10 coinsOfValue: 0.10. + paid := (w coinsFor: 0.6). + self assert: paid money equals: 0.6. + self assert: (paid coinsOfValue: 0.5) equals: 1. + self assert: (paid coinsOfValue: 0.1) equals: 1. ``` ### Conclusion What this example shows is that while a wallet is essentially a bag, having a wallet is a much more powerful solution. The wallet encapsulates an internal representation and builds a more complex API around it. \ No newline at end of file diff --git a/Chapters/Wallet/WalletSolution.md b/Chapters/Wallet/WalletSolution.md new file mode 100644 index 0000000..77f9a91 --- /dev/null +++ b/Chapters/Wallet/WalletSolution.md @@ -0,0 +1,46 @@ +## Electronic wallet solution @cha:walletSol Here are the possible solutions of the implementation we asked for the Wallet Chapter *@cha:wallet@*. ### Using a bag for a wallet ``` Wallet >> add: anInteger coinsOfValue: aCoin + "Add to the receiver, anInteger times a coin of value aNumber" + + bagCoins add: aCoin withOccurrences: anInteger ``` We can add elements one by one to a bag using the message `add:` or specifying the number of occurences of the element using the message `add:withOccurrences:`. ``` Wallet >> coinsOfValue: aNumber + + ^ bagCoins occurrencesOf: aNumber ``` ### Testing money ``` Wallet >> money + "Return the value of the receiver by summing its constituents" + | money | + money := 0. + bagCoins doWithOccurrences: + [ :elem : occurrence | + money := money + ( elem * occurrence ) ]. + ^ money ``` ### Checking to pay an amount ``` Wallet >> canPay: amountOfMoney + "returns true when we can pay the amount of money" + ^ self money >= amountOfMoney ``` ### Biggest coin ``` Wallet >> biggest + "Returns the biggest coin with a value below anAmount. For example, {(3 -> 0.5) . (3 -> 0.2) . (5-> 0.1)} biggest -> 0.5" + + ^ bagCoins sortedElements last key ``` ### Biggest below a value ``` Wallet >> biggestBelow: anAmount + "Returns the biggest coin with a value below anAmount. For example, {(3 -> 0.5) . (3 -> 0.2) . (5-> 0.1)} biggestBelow: 0.40 -> 0.2" + + bagCoins doWithOccurrences: [ :elem :occurrences | + anAmount > elem ifTrue: [ ^ elem ] ]. + ^ 0 ``` ### Improving the API ``` Wallet >> addCoin: aNumber + "Add to the receiver a coin of value aNumber" + + bagCoins add: aNumber withOccurrences: 1 ``` ``` Wallet >> removeCoin: aNumber + "Remove from the receiver a coin of value aNumber" + + bagCoins remove: aNumber ifAbsent: [] ``` ### Coins for paying: First version ``` Wallet >> coinsFor: aValue + "Returns a wallet with the largest coins to pay a certain amount and an empty wallet if this is not possible" + | res | + res := self class new. + ^ (self canPay: aValue) + ifFalse: [ res ] + ifTrue: [ self coinsFor: aValue into2: res ] ``` ``` Wallet >> coinsFor: aValue into2: accuWallet + | accu | + [ accu := accuWallet money. + accu < aValue ] + whileTrue: [ + | big | + big := self biggest. + [ big > ((aValue - accu) roundUpTo: 0.1) ] + whileTrue: [ big := self biggestBelow: big ]. + self removeCoin: big. + accuWallet addCoin: big ]. + ^ accuWallet ``` \ No newline at end of file diff --git a/index.md b/index.md new file mode 100644 index 0000000..a57107f --- /dev/null +++ b/index.md @@ -0,0 +1 @@ + # Getting in touch with Pharo % ${inputFile:Chapters/PlayingWithTurtles/PlayingWithTurtles.pillar}$ %removed from now the table % !!Playing with Joe the box % ${inputFile:Chapters/Katas/GramVariation.pillar}$ # About objects and classes In this part of the book we suggest carefully reading the first chapter before continuing with the rest. The other chapters contain extremely simple exercises which may be tedious to read in one sitting. % % Split in two files and use the existing files ObjectsAndClass.pillar % !! A graphic Turtle and jox the box % !! Do we do Joe the Box? % for this we should do the Turtle % ${inputFile:Chapters/JoeTheBox/JoeTheBox.pillar}$ % ${inputFile:Chapters/Converter/Converter2.pillar}$ % ${inputFile:Chapters/Tamagoshi/Tamagoshi.pillar}$ # Sending messages # Looking at inheritance # Little projects % Little collection manager % ! Solutions % ${inputFile:Chapters/GettingStarted/ChallengingYourselfSolution.pillar}$ % ${inputFile:Chapters/Katas/GramKatasSolution.pillar}$ % ${inputFile:Chapters/Wallet/WalletSolution.pillar}$ % ${inputFile:Chapters/Converter/ConverterSolution.pillar}$ % ${inputFile:Chapters/DSL/DSLSolution.pillar}$ % ${inputFile:Chapters/Expressions/ExpressionsSolution.pillar}$ % ${inputFile:Chapters/SimpleLAN/SimpleLANSolution.pillar}$ % ${inputFile:Chapters/SnakesAndLadders/SnakesAndLaddersSolution.pillar}$ \ No newline at end of file diff --git a/index.pillar b/index.pillar deleted file mode 100644 index fc3215c..0000000 --- a/index.pillar +++ /dev/null @@ -1,73 +0,0 @@ -${inputFile:path=Chapters/Introduction/Introduction.pillar}$ - -! Getting in touch with Pharo - -%${inputFile:Chapters/PlayingWithTurtles/PlayingWithTurtles.pillar}$ - -${inputFile:path=Chapters/GettingStarted/GettingStarted.pillar}$ - -${inputFile:path=Chapters/GettingStarted/ChallengingYourself.pillar}$ - -%!!Playing with Joe the box - -${inputFile:path=Chapters/Counter/Counter.pillar}$ - -${inputFile:path=Chapters/Tests/Tests.pillar}$ - - -${inputFile:path=Chapters/Katas/GramKatas.pillar}$ - -%${inputFile:Chapters/Katas/GramVariation.pillar}$ - -! About objects and classes - -In this part of the book we suggest carefully reading the first chapter before continuing with the rest. -The other chapters contain extremely simple exercises which may be tedious to read in one sitting. - -${inputFile:path=Chapters/OOPNutshell/OOPNutshell.pillar}$ -%% Split in two files and use the existing files ObjectsAndClass.pillar - - -% !! A graphic Turtle and jox the box -% !! Do we do Joe the Box? -% for this we should do the Turtle -% ${inputFile:Chapters/JoeTheBox/JoeTheBox.pillar}$ - -%${inputFile:Chapters/Converter/Converter2.pillar}$ -${inputFile:path=Chapters/Converter/Converter.pillar}$ - -${inputFile:path=Chapters/Wallet/Wallet.pillar}$ - -%${inputFile:Chapters/Tamagoshi/Tamagoshi.pillar}$ - -${inputFile:path=Chapters/DSL/DSL.pillar}$ - -! Sending messages - -${inputFile:path=Chapters/MessageSending/MessageSending.pillar}$ - -! Looking at inheritance - -${inputFile:path=Chapters/Inheritance/Inheritance.pillar}$ -${inputFile:path=Chapters/Inheritance/Extending.pillar}$ -${inputFile:path=Chapters/Expressions/Expressions.pillar}$ - -! Little projects - -${inputFile:path=Chapters/SimpleLAN/SimpleLAN.pillar}$ -${inputFile:path=Chapters/SnakesAndLadders/SnakesAndLadders.pillar}$ -${inputFile:path=Chapters/TinyChat/TinyChat.pillar}$ - -% Little collection manager - -%! Solutions -%${inputFile:Chapters/GettingStarted/ChallengingYourselfSolution.pillar}$ -%${inputFile:Chapters/Katas/GramKatasSolution.pillar}$ -%${inputFile:Chapters/Wallet/WalletSolution.pillar}$ -%${inputFile:Chapters/Converter/ConverterSolution.pillar}$ -%${inputFile:Chapters/DSL/DSLSolution.pillar}$ -%${inputFile:Chapters/Expressions/ExpressionsSolution.pillar}$ -%${inputFile:Chapters/SimpleLAN/SimpleLANSolution.pillar}$ -%${inputFile:Chapters/SnakesAndLadders/SnakesAndLaddersSolution.pillar}$ - - diff --git a/pillar.conf b/pillar.conf index 94f47e6..9274e55 100644 --- a/pillar.conf +++ b/pillar.conf @@ -1,16 +1 @@ -{ - "base_url": "/booklet-LearningOOP/html", - "site_name": "LearningOOP", - "title":"Learning Object-Oriented Programming, Design and TDD with Pharo", - "attribution":"Stéphane Ducasse", - "series": "The Pharo TextBook Collection", - "keywords": "Introduction, programming, design, testing, Pharo, Smalltalk", - "language": "en-UK", - "epub-id": "urn:uuid:A1B0D67E-2E81-4DF5-9E67-A64CBE366809", - "tocFile": "learningoop.pillar", - "latexWriter" : #'latex:sbabook', - "plugins": ["PRCitationTransformer"], - "bibFile": "others.bib", - "newLine": #unix, - "htmlWriter": #html -} \ No newline at end of file +{ "language" : "en-UK", "base_url" : "/booklet-LearningOOP/html", "attribution" : "Stéphane Ducasse", "keywords" : "Introduction, programming, design, testing, Pharo, Smalltalk", "title" : "Learning Object-Oriented Programming, Design and TDD with Pharo", "newLine" : "unix", "series" : "The Pharo TextBook Collection", "htmlWriter" : "html", "epub-id" : "urn:uuid:A1B0D67E-2E81-4DF5-9E67-A64CBE366809", "latexWriter" : "miclatex:sbabook", "site_name" : "LearningOOP", "bibFile" : "others.bib", "tocFile" : "learningoop.pillar" } \ No newline at end of file