Quick Intro Notes on the Smalltalk Language

[I'm posting this for some friends who have asked about Smalltalk.]

"Smalltalk" refers to both the Smalltalk language and the Smalltalk environment. I've forgotten a lot about the environment, and even more about the standard libraries, but I remember the language pretty well, partly because Objective-C is a hybrid of C and Smalltalk. Here's a quick intro; hope it helps.

The one main thing to know is that Smalltalk is all about objects sending messages.

  • Everything is an object.
  • Smalltalk code does only two things: assign variables and send messages.
  • Objects are instances of classes, which are themselves objects.
  • Classes live in an inheritance hierarchy.

An object is a thing that:

  • has state (in the form of instance variables),
  • has behavior (in the form of methods, which are named blocks of executable code associated with the object's class), and
  • can respond to messages.

There are only objects. That includes numbers, strings, windows, menus, classes, code blocks (including methods), and so on. Anything that's a "thing" is an object. There are no enums, no structs, no C-like "primitive" types as in Java, Objective-C, or C++. Only objects.

A message consists of a method name, called the selector, plus zero or more arguments. An object responds to a message by executing its corresponding method, if it has one. The code inside the method sends messages to other objects.

So: an object can respond to messages, and a message is a thing you can tell an object to do. An object responding to a message is called the receiver.

The terms "message" and "method" are sometimes used interchangeably, but it's important to understand the distinction. A message is like an interoffice memo that says "do X". A method is like the page titled "X" in the employee handbook. Note that different receivers of the same message can have different employee handbooks, and may therefore do "X" differently.

Although methods are essentially functions, this message-sending paradigm is different from the function-call paradigm in procedural programming. When you call a function in C, you identify the function by name and specify zero or more arguments. For example:

moveToXY(myTurtle, 5, 10);

By contrast, when you send a message in Smalltalk, you're asking an object to respond to a combination of method name and arguments. The receiver then decides what code to execute — roughly speaking, what function to call. For example:

myTurtle moveToX:5 y:10.

In this statement:

  • The receiver is myTurtle.
  • The message being sent to myTurtle is moveToX:5 y:10.
  • The selector in that message is #moveToX:y:, pronounced "move to x colon y colon". (IMO it's okay to just say "move to x y" for brevity if it's clear you don't mean moveToXY:.)
  • The arguments in the message are 5 and 10.

Your code is allowed to send any message to any object. When you run the code, the object will figure out upon receiving the message whether it knows how to respond. (Well, technically it's the Smalltalk runtime that figures this out.)

Every Smalltalk statement, other than variable assignment, has the same form as above. That's almost all the syntax you need to know. In particular, there is no control-flow syntax. If-then, while-do, for-each, and so on are done with message sends. Historically, the answer to "How on earth is that possible?" has been one of the first "aha's" people experience when they're new to Smalltalk. These days, with languages like Ruby in the mainstream, it's not so much of an "aha".

I'm running out of steam so that's all for now. 🙂

Notes After Finishing Advent of Code 2016

I finished this year's Advent of Code. I'd love to go back and clean up my code and write notes about each problem and the process I went through and the things I learned. Maybe I'll get around to that, maybe not.

Here are some ways of doing things that I found useful.

  • Write code to help visualize the problem. Helps me; YMMV.

    • Write code that draws pictures of the data. Take a hint from the problem descriptions — generate pictures similar to theirs. I found this helpful for the maze problems, among others. And sometimes it just makes things more fun.

    • For one problem I found that simply writing code to generate the following from the input helped me feel less confused about what had to be what mod what:

      we want t % 13 == 11
      we want t % 19 == 7
      we want t % 3 == 1
      we want t % 7 == 2
      we want t % 5 == 2
      we want t % 17 == 6
    • Another textual "visualization" aid: I used comments and indentation to mark up the "assembunny" code so that I could understand better what it was doing (here's what I did).

    • I didn't do much analog drawing (on paper or whiteboard), only a little during the later exercises. I used to be a compulsive whiteboarder — I'd like to recultivate that instinct.

  • If something feels like a hint it probably is, at least for AoC.

    • Example: for some reason Day 22 (moving data from disk to disk) was by far the hardest for me. I'm not sure how much or or how little that was due to inherent difficulty of the problem. But I do know I kept sabotaging myself by not letting the hints sink in, even when I was vaguely aware that the author of the problem was trying to tell me something.
  • Study the data — look for simplifying assumptions you can make that might help you get the answer by solving a less general case than the problem description alone might lead you to believe.

    • Day 22 is an example where this made all the difference in the world. Another that comes to mind is the "assembunny" problems.
  • If something is taking crazy long to execute, you may either have misunderstood or misread the problem, or you may be overlooking a hint.

  • Instrument your code with printf's that periodically show things like the size of your cache. This can provide clues to where there are leaks or inefficiencies in your code. For example, I realized my A* implementation was not properly putting nodes in the closed set, which led to an explosion of the open set — no wonder it was taking so long!

  • Testing. Do regression tests. Test the example data, which should run almost instantaneously, before trying your solution on Part 1. If Part 2 requires a lot of additional work, see if you can do sanity checks along the way to see if your code still solves Part 1. Sometimes your work on Parts 1 and 2 will be different enough that you can't really do this.

Swift vs Python for Advent of Code

Lately my daily addiction has been Advent of Code. It's a two-part programming challenge posted every day at midnight from Dec 1 to Dec 25. As long as you submit the right answer, you can use any language you like, or pencil and paper for that matter. It's not like some programming-challenge websites where they validate your solution by executing code that you submit.

I've been going back and forth between Swift and Python, both languages I'm new at. It's been a useful learning experience, both at the nitty-gritty level of language details and a more meta level about how I could maybe solve problems better. Sometimes I port my solutions from one language to the other, either to compare how the languages feel, to compare how the code performs, or simply for practice.

Lately I've been strongly preferring Python as my go-to language for these exercises. I have three main reasons.

Reason One: my Python code launches quicker without needing a moment to compile like Swift does, which means I can test and iterate faster, which means more immediate gratification.

Reason Two: simple string manipulation and array slicing are much quicker to code in Python, which means I do less typing and my simple intentions aren't buried in syntax. I wonder how daunting the learning curve seems regarding Swift strings, whether for new programmers or programmers coming to Swift from other languages. I wonder if there are some simplifications I'm missing.

Reason Three: it's trivial in Python to get an MD5 hash, which a few of this year's exercises have been requiring. People have written Swift wrapper code that does MD5 hashes, but every time I start to explore my options I think, "I'd rather work on solving the puzzle I wanted to solve, and I can do that right now in Python." Again, immediate gratification. Also, the Swift wrappers I've seen all require using an Objective-C bridge, which as far as I know requires using an Xcode project, which makes things heavier than I'd like.

One thing I'd like to revisit and get better at is using Swift playgrounds (with a lowercase "p" as in Xcode, not an uppercase "P" as in iPad; it drives me curse-out-loud nuts when I try to Google for the former and only get results for the latter). I tried using playgrounds for the early Advent of Code exercises, but I felt too attached to using breakpoints and lldb, which aren't available in playgrounds. Instead I've been using CodeRunner, which conveniently integrates debuggers for both Swift and Python. It's just right for this kind of lightweight coding and experimenting.

Here's my code. Note that this is not production-quality code. It's often sloppy, it often misses opportunities to solve the problem in a smarter way, it almost always assumes valid input, it's under-commented, and in at least one case it's way over-engineered.

Some Ways I Use Keyboard Maestro

Today, thanks to Michael Tsai, I found out there's a major new release of Keyboard Maestro. I use Keyboard Maestro all day long, and look forward to checking out the upgrade when I have time.

Here are some ways Keyboard Maestro makes my day go much more smoothly:

  • I use the palette feature to navigate to frequently used apps and documents in exactly two keystrokes.

  • I have a "Preferred Window Frame" macro that sets the active window's size and position. My preferred frame varies from app to app, and the macro does the right thing depending on which app is active. Just one keystroke to remember. The macro invokes keyboard shortcuts I've configured in Divvy, but I could also have entered window coordinates directly into Keyboard Maestro.

  • I map ^P/^N to UpArrow/DownArrow so I can use those emacs keys to go up and down in lists, menus, and other places outside of text editing. The ^P/^N keys already work in many apps, but not all, and not in all places within the same app. By setting up this global mapping, I don't have to remember where they work and where they don't.

  • You can create text expansion macros. I have macros for inserting date and time, for a few phrases I type on a regular basis, and for emoji characters. I was happy using Keyboard Maestro for all this but recently moved my text expansion macros to aText, a very nice app at a very reasonable price. My reasons for switching were minor and arguably not worth the time when I had a perfectly good solution. I wouldn't and perhaps shouldn't have bothered, except I am such an obsessive yak shaver.

  • I sometimes use Keyboard Maestro instead of System Preferences to provide alternate menu shortcuts. I do this because Keyboard Maestro syncs my macro definitions on Dropbox, which means that when I make a change, all my Macs automatically get it.

  • I open Xcode's Recent Files menu with one keystroke (I use ^R) instead of three (^1, DownArrow, RightArrow).

  • I have a scratch macro named "Pipe Text" that replaces the currently selected text by piping it through a shell script and pasting the result. The macro has an "Execute Shell Script" action that I edit to do what I have in mind, typically a grep.

  • I keep a personal log in the form of daily text files. I have a macro that opens today's log file in BBEdit, after creating the file if necessary. I use this throughout the day whenever I think of something to add to the file.

I'm happy to share how I do any of the above.