© Alexandre Bergel 2022
A. BergelAgile Visualization with Pharohttps://doi.org/10.1007/978-1-4842-7161-2_3

3. Pharo in a Nutshell

Alexandre Bergel1  
(1)
Santiago, Chile
 

Programming is the skill that allows you to tell a computer what it has to do. Effective data visualization cannot be properly exercised without programming. This chapter is an introduction to the Pharo programming language. Although I have tried to make this chapter smooth and easy to read, having some basic programming knowledge will help you better understand it, and indeed the book as a whole.

Pharo is an object-oriented, class-based, dynamically typed programming language. Pharo promotes live programming at its heart, and it does this very well. If these statements do not make much sense to you, no worry, as you will explore these important topics in this chapter.

The chapter begins with the ubiquitous “hello world” example accompanied by a brief introduction of what programming with objects is all about. The focus of the chapter is about Pharo itself; you will learn how to develop a small application to visualize tweets.

All the code provided in this chapter is available at https://github.com/bergel/AgileVisualizationAPressCode/blob/main/01-03-PharoInANutshell.txt.

Hello World

Instead of giving a long rhetorical description of object-orientation, this section uses a simple example. You need to write code in the Playground. You can open the Playground from the menu toolbar. The code is executed by pressing the green button (Do It) or by pressing Cmd+G or Control+G, depending on whether you use macOS or Windows/Linux, respectively. Type and execute a very simple Pharo script, as shown in Figure 3-1.
c := RSCanvas new.
c add: (RSLabel new fontSize: 30; text: 'Hello World').
c openOnce
../images/489192_1_En_3_Chapter/489192_1_En_3_Fig1_HTML.jpg
Figure 3-1

Hello World

Executing the script should open a new window in which you see the text Hello World, as shown in Figure 3-1.

RSCanvas refers to a class. A class is an object factory, and creating an object is like baking cakes. All the cakes produced in a pan have the same physical aspects, but attributes, such as flavors and colors, may vary. A class can produce objects that are different in the values of the variables; however, the variable names and the number of variables remain constant among all objects issued from the same class. A class is easily recognizable in Pharo code because its name always begins with a capital letter. The code given previously uses two classes, RSCanvas and RSLabel.

The first line creates a canvas object. An object is created by sending the new message to a class. For example, the RSCanvas new expression sends the new message to the RSCanvas class (which is also an object by the way). As such, it creates a canvas. Similarly, String new creates an empty string character and Color new creates a black color. The canvas, from executing RSCanvas new, is said to be the object produced by RSCanvas.

Objects interact with each other by sending messages. Consider the expression 'hello world' asCamelCase, which evaluates to 'HelloWorld' (simply type 'hello world'asCamelCase in the Playground, right-click the line and execute the item Print it). This expression sends the asCamelCase message to the string object 'hello world'. In Pharo, a class is also an object, which means that objects are created by sending a message to a class. The new message is sent to the RSCanvas class and it has the effect of creating an object canvas. This object is assigned to the variable c using the assignment operator :=.

In the second line of this script, the new message is sent to the RSLabel class, which creates an object called label. The fontSize: message is sent to that object with 30 as its argument, which has the effect of setting the font size to 30 units. Thanks to the cascade operator (the semicolon ;), the Hello World message text is sent to the label. It configures the label with the provided string. The configured label is then added to the c canvas when it’s provided to the add: message.

Visualizing Some Numbers

Consider this second example, which has been slightly more elaborated (see Figure 3-2).
c := RSCanvas new.
#(20 40 35 42 10 25) do: [ :v |
    c add: (RSCircle new size: v; model: v).
].
RSHorizontalLineLayout on: c shapes.
c shapes @ RSLabeled.
c @ RSCanvasController.
c openOnce
../images/489192_1_En_3_Chapter/489192_1_En_3_Fig2_HTML.png
Figure 3-2

Circles representing numbers

This example renders six circles, each having a particular size. The expression #(20 40 35 42 10 25) defines an array containing some numbers. A collection can be iterated using the do: message with a one-parameter block as its argument. This block is considered a function that is executed for each value contained in the array. An expression such as [ :v | ... ] defines such a block that accepts one argument. A block accepting two arguments would be written as [ :arg1 :arg2 | ...] in which the arguments are arg1 and arg2.

The RSCircle new size: v; model: v expression creates a circle with a determined size and model object. In Roassal, each visual element can represent an object, which is designed as a model. The model is used when applying interactions, such as RSLabeled.

Roassal offers several layouts to arrange shapes in a two-dimensional space. This example applies the horizontal line layout to all the shapes contained in the canvas. The c shapes @ RSLabeled expression sends the shapes message to the canvas c to obtain all the shapes contained in the canvas. The @ RSLabeled message is sent to the collection of shapes, which individually add the RSLabeled interaction to each shape. This interaction locates a textual label on each shape. The name of the message is @, while the argument is the RSLabeled class.

From Scripts to Object-Oriented Programming

We informally defined a set of instructions ready to be typed and evaluated in the Playground as a script. Scripts are great because they are usually self-contained and contain all the necessary logic to be easily understood.

Most visualization engines and data analysis environments operate on this principle: scripts are typed in a workspace or a web page and are then executed to produce a visualization. This approach to build a visualization or a small application is appealing since you do not need to look somewhere else to understand the logic of the script. However, this way of developing software artifacts has serious limitations.

Long scripts are difficult to maintain and modify in the long term. Imagine coming back to a script that’s several hundred lines of code after a few months have passed. Understanding the reasons and the structure of the script is usually difficult. So many details are provided in a small portion of the screen! If it’s not properly structured, adapting a complex visualization may consume a ridiculously large amount of time. This situation is well known to data scientists and software engineers. Fortunately, a couple of decades ago, the software engineering research community produced a way of programming that an cope with the inherent complexity of software artifact development. Object-oriented programming is the most successful way to handle complex and large development. As such, I encourage you to apply object-orientation to build visualization instead of using linear scripts.

Pillars of Object-Oriented Programming

Object-oriented programming simplifies the programming activity. Handling objects, instead of functions or code snippets, involves a metaphor that is familiar to humans: an object may act on some actions, have a behavior of its own, and hide details about how it is physically built.

Let’s bring a bit of theory into this. There are five essential ingredients of an object-oriented system:
  • Encapsulation: In your daily life, you are used to handling information that is not publicly accessible, e.g., your social security number, bank account number, and so on. Encapsulation in object-oriented systems is about letting objects have private information. Private information may reflect detail that is not directly necessary to a service consumer. When private information has to be publicly exposed, asking a question is the polite and cordial way to access it. In object-oriented programming, sending a message is the way to obtain information and carry out any computation. Encapsulation in object-oriented programming enables abstractions and information hiding, considerably easing the maintenance and evolution of software artifacts.

  • Composition: A complex problem may be solved by cutting it into smaller problems. Once these smaller parts are solved, a number of independent results have to be composed to form the result of a complex problem.

  • Distribution of responsibilities: In your daily life, you have duties and responsibilities. Having a clear separation of concerns is key to having a good object-oriented design, which also greatly contributes to understanding and maintaining software artifacts. For example, instead of asking someone’s weight in order to select what may be eaten, it is better for everybody to let that person make a responsible choice. This example is not far stretched: many difficulties in software maintenance are directly rooted from improperly assigned responsibilities in software.

  • Message sending: Electronic emails are the base of daily communication. A person, called the sender, sends an email to another person, called the receiver. In object-oriented programming, objects communicate in a similar fashion. Computation is carried out by sending messages between objects. An object sends messages to other objects. After sending a message, a reply is returned. In object-orientation, sending a message is often perceived as a way to delegate responsibilities.

  • Inheritance: This is a general concept that is specialized to address particular requirements. Inheritance allows you to define a conceptual hierarchy, reuse code, and support polymorphism. Inheritance may say that an ellipse and text are two graphical shapes.

These five pillars are not tied to one programming language. So, in theory, it is perfectly okay to have an object-oriented design in a procedural language such as C. However, having a programming language that enforces these principles greatly alleviates the programmer’s task.

There are numerous object-oriented languages around and Pharo is one of them. Pharo differs from other languages by offering an homogeneous way of expressing computation: everything is an object, therefore computation only happens by sending messages. When objects are taken seriously, there is no need for primitive types (e.g., int and float) or language operators! Having to deal only with message sending significantly reduces the amount of technological details associated with the program execution.

The following sections explain some of these concepts and illustrate how Pharo supports an elegant way to program with objects.

Sending Messages

Sending a message is the elementary unit of computation. Understanding how to send a message is key to feeling comfortable in Pharo. Consider the expression:
'the quick brown fox jumped over the lazy dog' copyReplaceAll: 'fox' with: 'cat'

This expression sends a message to the string object 'the quick brown fox jumped over the lazy dog'. The message has the name #copyReplaceAll:with: and two arguments, 'fox' and 'cat', which are two string objects. The result of sending this message is 'the quick brown cat jumped over the lazy dog', another string.

In Pharo, a character string (often simply called a string) is a collection of characters written between two quote marks (e.g., 'fox'). A string is an object, which means you can send messages to it. In Pharo, a message is composed of two essential ingredients: a name and a collection of arguments. It may be that the set of arguments is empty. For example, the 'fox'asUppercase expression, which evaluates to 'FOX', sends the #asUppercase message to the 'fox' string and no arguments are involved here.

Message sending is at the heart of the Pharo programming language, and as such, messages are well expressed in its syntax. There are three kinds of messages that can be sent:
  • Unary messages: A unary message does not take an argument. The expression 'fox'asUppercase sends a unary message to the string 'fox'.

  • Binary messages: A binary message has exactly one argument and its name is not made of alphanumerical characters. Instead, one or two characters are common for binary messages, such as +, /, -, <, and >>. The expression 2 + 3 sends a binary message named + with the argument 3 to the object 2. You may notice that this expression is therefore semantically different from 3 + 2, although the result is obviously the same. Note that the expression 3 + 2 * 2 returns 10, and not 7 as you may expect. If you want to enforce mathematical priorities in arithmetic operations, use parentheses, as in 3 + (2 * 2). In practice, not having priorities between mathematical operators is not a problem. Inserting parentheses is cheap and does not hamper readability (most of the time it increases readability).

  • Keyword messages: A keyword message is neither unary nor binary. A keyword message accepts one or more object arguments. Consider the example 'the quick brown fox jumped over the lazy dog'includesSubstring 'fox'. This expression evaluates to true. The name of the keyword message is #includesSubstring: and the argument is 'fox'. Each argument is preceded by a keyword. For example, the message replaceAllRegex : 'fox'with: 'cat' contains two keywords and therefore two arguments. Arguments are inserted in the message name.

Sending a message triggers a mechanism that searches for the adequate method to execute. This mechanism, often called method lookup, begins at the class of the object and follows the superclass link. If a method with the same name as the message is not found in the class of the object, the method lookup searches in the superclass. The process is repeated until the method is found. When a message sent, the keyword super triggers a lookup that begins in the superclass of the class that contains the call on super.

Creating Objects

An object is a bundle of data to which messages can be sent. Most of the time, an object is created by simply sending the new message to a class. This reveals the true nature of classes, being an object factory. Objects produced from a unique class are different but understand the same set of messages and have the same variables. Differences between two or more objects issued from the same class are the values given to these variables. For example, consider the following expression:
Object new == Object new

This expression sends three messages, the new message twice and the == message once, to compare object identities. The expression evaluates to false, since the two objects are different, i.e., located in different physical memory locations.

The Point new expression creates a point by sending the new message to the Point class. There are several ways to create a point:
  • Point new creates a point (nil, nil), a point in which both x and y are left uninitialized and therefore have the nil value. All classes in Pharo understand the new message. Except when explicitly prohibited, an object is created by sending new to the class.

  • Point x: 5 y: 10 creates a point (5, 10). This expression sends the message #x:y: to the class Point, with 5 and 10 as the arguments. The Point class defines the class method named #x:y:. The difference between using new and x:y: to create a point is that the latter allows you to create and initialize a point with a given value for x and y.

  • 2 @ 3 sends to the object 2 the message named @ with the argument 3. The effect is the same as with Point x: 2 y: 3, which is to create the point (2, 3).

A class may have its own way to create objects. For example, a point is not created the same way that a color is created. Creating an object is also commonly called “instantiating a class” and an object is often referenced as an “instance”.

A class is an object factory and an object is necessarily created from a class. As discussed, objects interact each other by sending messages. An object can understand messages corresponding to methods defined in its class, and methods defined in the chain of superclasses.

Creating Classes

A class is both a factory and abstraction of objects. You need to create classes as soon as you want to bundle logic and data together. In Pharo, a program is necessarily structured as a set of interacting classes.

Pharo comes with a few thousand classes, and they need to be structured in some way so as not to overwhelm programmers. Pharo, like most programming languages, offers the notion of the package, which is essentially a container of classes. A class belongs to a package. A good practice is to create a package to contain the classes you will define:
  1. 1.

    Open a system browser from the top menu.

     
  2. 2.

    Right-click the top left list panel (see Figure 3-3) and define a package called TweetsAnalysis.

     
  3. 3.

    Create the Tweet class. A class is created by adding the following to a code browser:

     
../images/489192_1_En_3_Chapter/489192_1_En_3_Fig3_HTML.jpg
Figure 3-3

Creating a new package

Object subclass: #NameOfSubclass
    instanceVariableNames: ''
    classVariableNames: ''
    package: 'TweetsAnalysis'

The system browser is a standard tool in Pharo that allows you to browse and edit the source code. The previous code is a template for class creation. The NameOfSubclass text has to be replaced with the name of the class you want to create. After the instanceVariableNames: keyword, you need to provide the instance variables, and after classVariableNames:, provide the class variables. A class variable is visible from instance methods and class methods. Note that we do not use class variables in this book, so the classVariableNames: field will always be left empty.

In the Tweet example, you will model a tweet as an object having content, a sender, and a date. You could therefore define the following Tweet class:
Object subclass: #Tweet
    instanceVariableNames: 'content sender date'
    classVariableNames: ''
    package: 'TweetsAnalysis'

Write (or simply copy and paste this definition from the GitHub link given at the beginning of the chapter) this class definition in the lower of in the code browser, right-click the pane, and select Accept. In Pharo jargon, accepting a class definition means creating and compiling the class.

Figure 3-4 shows the system browser after having created the Tweet class, which is contained in the TweetsAnalysis package.
../images/489192_1_En_3_Chapter/489192_1_En_3_Fig4_HTML.jpg
Figure 3-4

Creating a new class

Creating Methods

A method is an executable piece of code. A method is composed of instruction statements that carry out a computation. No methods have been defined so far. In this case, if you want to define meaningful tweet objects, you need to modify the values of the variables defined in the Tweet class. To access the date of a tweet, you define the method with the following source code:
Tweet>>date
    ^ date
The source code in this book uses the convention of preceding the method name with the class name. The code defines the date method in the Tweet class. The Tweet>> portion should not be typed. Select the Tweet class, choose the instance side Method tab, and enter the following:
date
    ^ date
You can now compile the code you just entered by pressing Cmd+S (on macOS) or Ctrl+S (on Linux and Windows platforms). Figure 3-5 illustrates the state of the code browser after compiling the method. The upper-right part of the system browser lists the methods defined in the selected class. As you can see, the method date is now part of the Tweet class.
../images/489192_1_En_3_Chapter/489192_1_En_3_Fig5_HTML.jpg
Figure 3-5

Creating a new method

The date method is useful for fetching the date from a tweet. The date: method allows you to set a date on a Tweet object:
Tweet>>date: aDate
    date := aDate
Similarly, the contents of a tweet can be retrieved using:
Tweet>>content
    ^ content
A tweet’s content can be set using:
Tweet>>content: aContent
    content := aContent
The sender of a tweet can be obtained using this method:
Tweet>>sender
    ^ sender
A sender can be set in a Tweet object using:
Tweet>>sender: aSender
    sender := aSender
Click the Class Side button in the system browser. It switches the system browser from the instance side to the class side: methods defined on that side will now be class methods, invocable directly on a class. Define the method:
Tweet class>>createFromURL: urlAsString
    "Method to be defined on the CLASS side"
    | content lines sender date |
    content := (ZnEasy get: urlAsString) contents readStream.
    lines := content contents lines collect: [ :l |
        | firstCommaIndex secondCommaIndex |
        firstCommaIndex := l indexOf: $,.
        secondCommaIndex := l indexOf: $, startingAt: (firstCommaIndex + 1).
        sender := l copyFrom: 1 to: (firstCommaIndex - 1).
        date := l copyFrom: (firstCommaIndex + 1) to: (secondCommaIndex - 1).
        content := l copyFrom: (secondCommaIndex + 1) to: l size.
        { sender . date . content }
    ].
    ^ lines collect: [ :line |
        Tweet new
            sender: line first;
            date: line second;
            content: line third ]
The createFromURL: method fetches the CSV file we have prepared for that example. Note that the comments in the code are double quoted in Pharo. The file contains 1,000 random tweets. It does a simple parsing of the content by splitting it using commas. Next, you can define the method:
Tweet class>>createFromExample
    "Method to be defined on the CLASS side"
    | url |
    url := 'http://agilevisualization.com',
            '/AgileVisualization/tweets.csv'.
    ^ self createFromURL: url
The provided URL is an example to illustrate the example in this chapter. You can open the http://agilevisualization.com/AgileVisualization/tweets.csv URL in a web browser to see what the CSV file looks like. At this stage, evaluating the expression Tweet createFromExample returns a list of 1,000 tweet objects, each tweet describing an entry of the online CSV file. See Figure 3-6.
../images/489192_1_En_3_Chapter/489192_1_En_3_Fig6_HTML.jpg
Figure 3-6

The createFromURL: method, defined in the Tweet class

You can define two new methods on the Tweet class. Switch to the instance side (i.e., select the Inst. side button in the system browser) and define the following two instance methods:
Tweet>>words
    "Return the list of words contained in a tweet"
    ^ self content substrings

The words method returns the list of words contained in a tweet. The words method uses substrings to return a list of words from a string, the content of the tweet. For example, the expression 'fox and dog'substrings returns #('fox''and''dog').

You can compare tweets using the method isSimilarTo:, which is defined as follows:
Tweet>>isSimilarTo: aTweet
    ^ (self words intersection: aTweet words) size >= 4

The isSimilarTo: method takes as an argument another tweet and returns true or false, depending on whether the tweet argument is similar to the tweet that receives the isSimilarTo: message. The notion of similarity used here is that two tweets are similar if they have at least four words in common. This is a simple and easy-to-implement heuristic, and it does not consider the actual semantics of the tweet; however, it is enough to build a structure between tweets.

Here’s a simple way to determine whether a tweet conveys a positive feeling:
Tweet>>isPositive
    ^ #('great' 'cool' 'super' 'fantastic' 'good' 'yes' 'okay' 'ok') includesAny: self words
To determine if a tweet is negative, define this method:
Tweet>>isNegative
    ^ #('bad' 'worse' 'down' 'no') includesAny: self words

Again, this example uses very simple heuristics to identify the feeling conveyed in a tweet. The goal is to illustrate relevant and important aspects of Pharo, not to convey the state-of-the-art in sentiment analysis.

For this scenario, you need to have a meaningful textual representation of a tweet. Currently, if you print a tweet by typing Tweet new and pressing Cmd+P/Ctrl+P, you will obtain "a Tweet", which is not really useful in this situation. You will now define the method printOn: to produce an adequate textual representation:
Tweet>>printOn: str
    | whatToPrint |
    whatToPrint := self content
                        ifNil: [ 'Empty' ]
                        ifNotNil: [ self content ].
    str nextPutAll: whatToPrint
You now have some objects and a way to establish a relationship between them based on a simple heuristic for similarity. This is enough to visualize the tweets and look for some patterns. Open the Playground and type the following (see Figure 3-7).
tweets := Tweet createFromExample.
tweetShapes := RSCircle models: tweets forEach: [ :aCircle :aTweet | aTweet isPositive ifTrue: [ aCircle color: Color green ].
 aTweet isNegative ifTrue: [ aCircle color: Color red ] ].
tweetShapes translucent.
c := RSCanvas new.
c addAll: tweetShapes.
RSLineBuilder line
    shapes: c nodes;
    color: Color gray translucent;
    withBorderAttachPoint;
    moveBehind;
    noBidirectional;
    connectToAll: [ :tweet |
            tweets select: [ :t | t isSimilarTo: tweet ] ].
c nodes @ RSPopup @ RSDraggable.
RSNormalizer size
    shapes: c nodes; from: 5; to: 15; normalize: [ :tweet | tweet content size ].
RSConditionalLayout new
 ifNotConnectedThen: RSGridLayout new;
 else: RSForceBasedLayout new;
 on: c nodes.
c @ RSCanvasController.
c open.
../images/489192_1_En_3_Chapter/489192_1_En_3_Fig7_HTML.png
Figure 3-7

Visualizing some tweets

The visualization shows a large cluster of connected tweets. Tweets that are not similar to any others are on the left side, using a grid layout.

Block Closures

A block closure (also simply called “block”) is a piece of code associated with an environment. A block is manipulable, as is any Pharo object, and can be provided as a message argument and be assigned to a variable. The expression [ :value | value + 5 ] is a block closure that takes one parameter and adds 5 to it. This block can be evaluated with an argument using the value: message. Consider the following code snippet:
b := [ :value | value + 5 ].
b value: 10. "Return 15"
b value: -5. "Return 0"
A block can accept zero, one, or more arguments. A block without any argument may be [ 42 ], and is evaluated by simply sending value to it. For example, [ 42 ] value returns 42. As you will soon see, this is particularly useful in control structures such as conditional statements. An example of a block with more than one argument could be:
b := [ :x :y | x + y ].
b value: 40 value: 2 "return 42"

Control Structures

Conditional statements are expressed using one of the following messages: ifTrue:ifFalse:, ifTrue:, or ifFalse:.

For example, the value (42 > 5)ifTrue: [ 'Great value'] is evaluated to 'Great value'. The argument provided to ifTrue: must have no argument.

The (42 > 5) expression evaluates to a boolean, to which the message ifTrue:ifFalse: is sent. This ifTrue:ifFalse: message takes two blocks as arguments. The first one is evaluated when the boolean receiver is true, and the second block is evaluated when the receiver is false.

Collections

A collection is a very common data structure and it is critical that you understand how to use them. As previously illustrated, the #(23 42 51) expression defines an array, which is an instance of the Array class. You can verify the class of an array by evaluating #(23 42 51)class, which gives Array. This class, and its superclasses, have a large number of methods. Data transformation and data filtering are two common operations typically performed on a collection.

A transformation is realized using collect:. For example, #(23 42 51)collect: [ :v | v > 30 ] returns #(false true true). The initial array of numbers is transformed as an array of booleans.

Filtering is carried out using select:. For example, #(23 42 51)select: [ :v | v > 30 ] returns #(42 51). Both collect: and select: take a block as an argument. In the case of select:, the block has to evaluate to a boolean.

Pharo’s collections are rich and expressive. You just saw the example of Array. Another useful collection is OrderedCollection, which represents an expandable collection. Elements may be added to and removed from an ordered collection during program execution. For example:
v := OrderedCollection new.
v add: 23.
v add: 42.
v add: 51.
shapes := v collect: [ :nb | RSBox new size: nb ].
RSVerticalLineLayout new alignCenter; on: shapes.
RSCanvas new
    addAll: shapes;
    yourself
This small script produces three squares lined up vertically. Another useful collection is Dictionary. A dictionary stores pairs of keys and values. For example, consider the following code snippet:
d := Dictionary new.
d at: #one put: 1.
d at: #two put: 2.
d at: #three put: 3.

The d at: #two expression returns the value 2 and the expression d at: #four raises an error.

Cascades

A cascade is expressed using the syntactical element and it allows you to send several messages to the same object receiver. For example, instead of writing:
v := OrderedCollection new.
v add: 23.
v add: 42.
v add: 51.
You can write:
v := OrderedCollection new.
v
    add: 23;
    add: 42;
    add: 51.

We this notation extensively when manipulating RSLineBuilder and RSNormalizer, as cascades considerably shorten the amount of code you need to provide.

A Bit of Metaprogramming

Pharo provides an expressive reflective API, which means you can programmatically get data about how Pharo code is structured, defined, and executed. Consider the RSShape methods size expression. This expression returns the number of methods that the RSShape class defines. The methods message is sent to the RSShape class , which is also an object in Pharo. This message returns a collection of the methods defined on the RSShape class. The size message is finally sent to that collection to obtain the number of methods defined in the RSShape class.

Many examples in Agile Visualization visualize software source code and use the reflective API. Visualizing source code is convenient because it does not need to fetch data from an external source of data, manipulating source code is trivial and very well supported in Pharo, and software source code is complex enough that it deserves to be visualized.

What Have You Learned in This Chapter?

This chapter gave a brief introduction to object-oriented programming. From now on, you should be able to understand Pharo syntax. I recommend a number of books to further discover the world of Pharo: Pharo by Example and Deep Into Pharo, both available for free from https://books.pharo.org.

Pharo is a beautiful, elegant, and simple language. It has a small and concise syntax, which makes it easy to learn. Its programming environment is also highly customizable.

Building a sophisticated visualization or any non-trivial software artifact often involves complex development. Mastering object-orientation is not strictly necessary in order to use Roassal and learn from through this book. However, having a good command of object-oriented programming will considerably alleviate your development and maintenance efforts.

Pharo offers a powerful meta-architecture. Do you remember that an object is created by sending the new message to a class? In Pharo, a class is also an object since you send new to it, as in the expression Color new. A class is therefore an object, itself an instance of another class, called a metaclass. And it does not stop here. A metaclass is also an object. Methods are also objects, each method being a collection of bytecodes. Many parts of Pharo are truly beautiful, but going into more detail is out of the scope of this book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.210.229