Skip to content

Instantly share code, notes, and snippets.

@tbrunz
Last active December 18, 2022 23:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tbrunz/02d9a2f62053f8dfa4c229e5075d6796 to your computer and use it in GitHub Desktop.
Save tbrunz/02d9a2f62053f8dfa4c229e5075d6796 to your computer and use it in GitHub Desktop.
Tips on getting started with Pharo Smalltalk
Tips on getting started with Pharo Smalltalk:
The Pharo Playground & Transcript
The Pharo IDE (specifically, a Playground plus the Transcript) is very useful
for creating and executing "code snippets" or even entire scripts in Pharo.
It's very easy to get a Pharo IDE installed and running by just downloading and
installing Pharo Launcher from https://pharo.org/, cloning a template image,
then launching the image.
The Pharo 'REPL' (Read-Execute-Print-Loop), or 'CLI' (command line interface)
is a little different in Pharo than in other interpreted languages such as
Bash, Lua, Julia, etc. Since some Pharo constructs need more than one line
(without forcing the use of escapes such as '\'), and because you have more
than one way to view the results of evaluating something in Pharo, its REPL
is necessarily different.
In the Pharo IDE, you start by opening a 'Playground' window, using the menu bar
at the top or by left-clicking the desktop (also called 'The World') to get
a context menu. A Playground window is like a cross between a 'smart editor'
and a CLI that allows you to compose and execute groups of multi-line Pharo
statements and expressions. You can open several Playgrounds at once, and even
join multiple windows into a single tabbed window if you wish.
You'll quickly realize that pressing 'Enter' doesn't evaluate code statements.
Instead, you just get a new line to continue composing. When you're ready to
evaluate your statements, you use the mouse (or the keyboard) to swipe & select
the lines (or any amount of text) you want to evaluate, then right-click on the
highlighted text to choose an action to take. (You can also press control-key
equivalents to limit mouse use.)
When evaluating selected text, you have three choices for what results you want:
1) Execute what you've selected but ignore the result ('Do it'/Ctrl-D),
2) Execute and display the result under the cursor ('Print it'/Ctrl-P), or
3) Execute and open an inspector on the result ('Inspect it'/Ctrl-I).
The cool thing is that this ability to select-and-execute text actually works
*anywhere* you can enter or select text in the Pharo environment -- anywhere,
in any window!
The Playground window adds an extra feature: You can tell it to take the entire
contents of the Playground, execute it top-to-bottom, and open an inspector on
the result in a second tab. This is 'Do it & go'/Ctrl-G, which has not only a
context menu item, but also a green 'Play' icon in the Playground toolbar.
(The 'Do it & go' inspector is also more full-featured: You can cascade it,
inspecting elements of the first result, elements of that inspection, etc.)
It's called a 'Playground' because it lets you "play around with code", e.g.,
to compose & test methods in an experimental fashion. Once you're satisfied
your ideas are working the way you want, you can swipe the code and copy/paste
it into a class method in a System Browser window. (Making your Playground
code into a method makes it persistent and call-able.)
One more thing you occasionally want a REPL to do is print things in a more
permanent form, i.e., log the results of evaluating some expressions. There's
a logging console window called the 'Transcript' for that, which is also opened
via the menu bar or from a left-click on the desktop. To print things on the
Transcript, you add the equivalent of 'print statements' to your Playground
script.
It might seem strange at first that the input and output for a REPL is split
between two windows. But it's actually a big advantage, especially when you
want to re-execute a group of lines you previously typed in the Playground:
You won't have your output intermixed with your command lines, so you can
easily re-select any of your input lines and run them again, which avoids
syntax errors and unwanted results caused by prompts and output lines being
included. (And many times you probably don't care to persist the output
anyway, especially if you're experimenting.) With the Transcript to capture
your results, you can retain everything you want to keep, and nothing that
you don't. This keeps your Playground clean, a nice feature!
Making Things Happen in Pharo
In Pharo, everything is done by sending messages to objects; there is no other
way to do anything! (It's a very simple programming paradigm...) So to get
the Transcript to 'log' the results of evaluating an expression, you send it
a message with an argument to be displayed. Here's a simple example:
circumference := 2 * Float pi.
Transcript crShow: circumference.
>>> 6.283185307179586
which will display the circumference of a unit circle in the Transcript. How
does this work? 'Float' is the class of floating-point numbers, and, since
everything in Pharo is an object, a class is both a class and an object itself:
it can receive and act on messages, too. We send Float the (unary) message 'pi',
causing it to return pi as a float object, which we then multiply by 2 (by giving
pi to '2' and asking it to multiply itself by this value). We assign the result
to our variable, 'circumference'. Note that a period separates Pharo statements,
just as with English sentences.
In the second statement, we send the Transcript object (also a class object)
the 'crShow:' message, which includes an argument, the object 'circumference'.
It responds by printing a newline in the Transcript window, followed by the
(default) string representation of the float held by 'circumference', which is
"6.283185307179586". (Remember that you can evaluate *anywhere* in Pharo?
You can also enter these expressions directly in the Transcript window and
evaluate them in the Transcript window, too!)
Here's a more involved example:
x := 7.
y := 3.
z := [ :offset | x factorial + offset ].
Transcript crShow: (z value: y squared).
>>> 5049
In this case we define variables 'x' & 'y' and give them values 7 & 3. Then
we define variable 'z'... What are we assigning to 'z'? In Pharo, we can
defer execution of code statements by enclosing any number of them in square
brackets, making a 'block closure' object. These 'blocks', as they are called,
can be assigned to variables, passed as method arguments, & returned as results.
A block can also be instructed to do things, such as evaluate itself (which is
similar to making a function call). Evaluating a block can includes providing
it with one or more variable arguments.
In our example above, we've defined one formal argument for our block, 'offset'.
Formal parameters are denoted by adding ':' to the front of a variable name,
then separating the parameter list from the body with a '|' character. (We can
also add local variables to the block, if needed, by following the first '|'
with a '| varList |' expression.) We are then free to use these locals and
formal parameters within the block body statements.
We can also use any other variables that happen to be in scope when the block
is defined, including variables defined in the enclosing code -- even when we
know the enclosing code will have completed execution and had its locals go out
of scope! We say that variables 'x' & 'y' are "lexically scoped" in the block
we're defining, and can be referenced when the block executes (even if their
values change after the block is defined at run time). In Pharo, all blocks
are this kind of 'lexical closure' (a very powerful property that not many
programming languages provide).
The block code in our example first sends the unary message 'factorial' to the
value 'x' holds (which will be '7', a SmallInteger instance), causing it to
evaluate '7!'. This results in 5040, another instance of SmallInteger. That
object, '5040', is then given the binary message '+ offset', which tells it to
add the value of 'offset' to itself. But 'offset' will not take a value until
the block is given an argument when it's told to evaluate itself...
That evaluation takes place in the last statement, where we send Transcript the
keyword message 'crShow:' (a message with one argument). The argument in this
case is the result of yet another expression, 'z value: y squared', which we
want evaluated first. We force this by the use of parentheses; without the
parentheses, Transcript would receive the two-argument 'keyword' message,
'crShow:value:', which it would not understand, as that message is undefined.
Within the parentheses, 'y' (an instance of SmallInteger) first evaluates the
unary message 'squared'; in this case, the '3' returns '9' as its result. The
object '9' becomes the argument for the keyword message 'value:' that is sent
to 'z'. Since 'z' holds a one-argument block, its block receives the 'value:'
message, using the argument (9) to set its internal parameter, 'offset'. Then
the block evaluates itself, computing '7! + 9', or '5049'.
Finally, this result is passed to the Transcript as an argument to the 'crShow:'
message, instructing Transcript to print a newline in the Transcript window,
followed by a string representation of the SmallInteger '5049'.
Why did we need parentheses only in one case here? Pharo has three types of
messages: unary, binary, and keyword; each is illustrated above. The order of
precedence in evaluation is: unary, binary, then keyword; evaluation is always
performed left to right. Parenthesis are used to group expressions to override
this precedence. However, in practice, expressions will often have the desired
order of execution and won't need parentheses. (One possibly surprising
exception is an expression such as "3 + 2 * 4", which evaluates to 20, not to
11. Why? Because there are no exceptions to the evaluation rules, so it's
strict left-to-right: 3+2= 5, and 5*4= 20. Use parenthesis to make conventional
arithmetic rules apply!)
Where to go Next
Knowing the above is enough to start evaluating things in the Pharo IDE. The
IDE also has tools to help you find things, such as classes and methods, for
doing more sophisticated things. One of the hardest things about learning any
OOP language is knowing what the base classes are and what/how they do things
for you. Pharo has probably the best built-in set of tools to make finding
things easy. It's also a very explorable environment, providing all its source
code for you to browse, and even modify! Many methods and classes also contain
documentation that includes examples for how to use them.
If you're interested in learning how to create your own Packages, Classes, and
Methods in Pharo, it's recommended to continue beyond "ProfStef" and read at
least "Pharo by Example" (PBE), the first tutorial of an excellent 3-booklet
series that teaches the basics. The current version is being written for
Pharo 9, but the Pharo 5 version is still relevant, as most changes involve
internal improvements, with some GUI differences. Current publications are here:
https://books.pharo.org/
Drafts of the soon-to-be-released version of PBE for Pharo 9 can be found
here:
https://github.com/SquareBracketAssociates/NewPharoByExample9/releases/tag/latest
There are other sources of help in getting started and getting answers to Pharo
questions, beyond just googling, including a Discord channel and several forums.
Refer to the main website, https://pharo.org/, for links.
@JamesStallings
Copy link

Awesome stuff fresh off the presses!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment