00:00:25.439
Good morning! Welcome to the MountainWest Smalltalk and Ruby Conference. My name is Randy Coleman. My wife says it's pronounced like "soul man"—so if you ever have trouble remembering, that's a little trick to help you. I find that I get my name pronounced in many different ways, and I take this opportunity to get it on one slide as many times as I can. I'm very creative with picking my usernames.
00:00:34.000
Today, I want to talk about affordances and how they relate to programming languages. This is a faucet that you might see in a hotel sink or bathtub. Can anybody figure out how it works? It's a faucet, right? You should be able to turn it on and get hot and cold water out of it. But actually, it's kind of hard to figure out without instructions. Fortunately, this one comes with instructions.
00:00:54.000
In the physical design world, there's a concept called affordances. I first heard about it in a book by Donald Norman, called "The Psychology of Everyday Things," which has been re-released as "The Design of Everyday Things." If you build anything that people are going to use, I highly recommend this book as it contains a lot of great advice. An affordance is a quality of an object or environment that allows someone to perform an action; it effectively communicates the action you can perform with it.
00:01:18.960
For instance, you see a button and you know you can push it; you see a knob and understand that you can turn it. These are the kinds of things we refer to as affordances. Let's look at a couple more examples. This is a crosswalk light, which shows bright red lights saying "push" but requires you to actually push a button below it. They even had to put arrows to indicate where to push because many people get confused.
00:01:37.040
Here’s another example—the back door of a bus. There are bars on the right and the left that you might think you can push, but you actually have to touch the yellow strip instead. Because that wasn’t clear enough, they added white arrows to communicate how to operate the door: how do you get off the bus? These examples illustrate poor affordances, but good affordances exist too.
00:02:02.080
For example, if you came up to an escalator that looked like this, it immediately communicates what you should do: you stand on the right if you want to stay still, and you walk down on the left. So, there are good and bad affordances. Affordances also apply to software, particularly in user interface (UI) design, where you want your UIs to communicate how they are supposed to be used.
00:02:29.760
Even API design can have affordances. There’s a person named Amir Rajan who has been producing a series of JavaScript screencasts called "Coding Out Loud." In one of his projects, he's building a tic-tac-toe game API. In his API documentation, he not only explains how to use the API but also provides a list of all the URLs you can access, given the current state of the game. This documentation affords you— the programmer— the ability to operate the game effectively.
00:02:58.640
In this talk, I want to suggest that affordances also apply to programming languages. Different languages afford different kinds of design and influence the solutions we create for problems. For instance, let's look at a simple example: the humble point class. I’m going to use some ideas from Smalltalk to propose a better solution in Ruby since this is a Ruby conference, and I’ll draw from the languages I’m most familiar with.
00:03:36.799
I program in Smalltalk and C++ every day, and increasingly, I’m utilizing Ruby as well. In Ruby, you might create a simple point class that allows you to instantiate points with X and Y coordinates. However, what happens when we want to support polar coordinates? Polar coordinates specify a point using a radius (or distance from the origin) and an angle from the positive X-axis.
00:04:12.239
Now, if we look at our simple point class in Ruby, we only have one constructor, taking two numbers. If we wanted to create a point based on polar coordinates, we might consider adding a parameter or some tag to designate the type of coordinates. When I worked with a colleague named Anna, she’d notice when I encountered poor solutions, and she would quip, 'You’re making the poo face again.' I associate trying to do that in programming with making that same expression.
00:04:46.960
So, turning to Smalltalk, we can see a similar point class. While the code may seem wordy in a flat representation, each method in Smalltalk uses keyword arguments. Each method also has a unique name. For instance, we define a class method on point called `x: y:` that takes two parameters, delegating to an internal method to initialize the instance variables. Ruby does this under the hood as well.
00:05:19.760
To support polar coordinates in Smalltalk, the solution is straightforward; just add another named constructor called `r: theta:`. This new method can accept a radius and an angle, delegating to the XY constructor using the right trigonometric formulas. You send the message `r: theta:` to point, and you successfully create a point in polar coordinates.
00:05:59.280
Smalltalk offers the affordance of named constructors. But can we do this in Ruby? Yes, we can create class methods in Ruby. Here's an example: I created an `xy` method that takes X and Y and delegates to the normal constructor (`new`). Then, I can create another constructor called `self.polar` that utilizes the radius and theta.
00:06:28.160
This method also follows the same logic of employing trigonometric formulas. A little bonus idea—though everything in Ruby isn't truly private, marking methods as private helps communicate how I intend them to be used. Thus, we can construct both types of points via calls such as `Point.xy` and `Point.polar`.
00:07:01.679
This just demonstrates how we can take an idea from Smalltalk—namely, named constructors—and apply it to Ruby to offer a solution for creating points using both coordinate systems. Now let's consider another straightforward example: the `find` method in Ruby, which is also known as `detect` in Smalltalk.
00:07:16.960
Suppose I want to search through an array of numbers for an odd number. This is how you might do it in Smalltalk. I won't overwhelm you with Smalltalk syntax. Essentially, the hash with parentheses denotes a literal array in Smalltalk, while square braces denote a block of code.
00:08:01.600
In Smalltalk, you use `detect` which will raise an exception if there is no matching element. The equivalent code in Ruby utilizes `find`, which returns nil if an element isn't found.
00:08:31.040
However, if I prefer returning something different, I have the option of passing multiple blocks to a method in Smalltalk. Using `detect:ifNone:` allows me to specify both the criteria I’m looking for and the fallback if I don’t find a matching number—whereas Ruby only returns nil.
00:09:04.000
Multiple blocks are an affordance in Smalltalk. Can we replicate this in Ruby? Yes! Ruby's `find` method supports an optional parameter, and you can use a callable to act if the search criteria can't be satisfied. However, it may not blend as seamlessly, and many programmers might not be aware of this feature.
00:09:35.360
Now, let's pivot to the concept of linguistic relativity, which you may recognize as the Sapir-Whorf hypothesis. It suggests that languages influence thought. The language you think in can shape the kinds of ideas and thoughts you are capable of. This intriguing idea has stirred some debate over the years.
00:10:01.600
Corey Foy delivered a talk recently in which he explained what a language allows or forces you to say. He illustrated this point with an example: if I said I went to the movies with my neighbor, you might inquire whether the neighbor is male or female.
00:10:28.240
Supposing I responded evasively, you might not get an answer. However, if I were speaking French, a gendered language, I would have to specify that detail due to the structure of the language. Similarly, programming languages influence our thoughts; they shape how we write code and solve problems.
00:10:52.239
Yukihiro Matsumoto, the creator of Ruby, emphasized this concept in a talk he presented at OSCON in 2003. He expressed that programming languages are not just tools for communication, but also for thought. One of his chief goals in designing Ruby was to create an effective thinking tool.
00:11:23.679
Now, let’s delve into slightly more complex examples. I apologize in advance, but I’m going to bring in some C++ concepts. When programming, we often need to clean up after ourselves because we work with finite resources such as memory, threads, file handles, database connections, and locks.
00:11:50.480
When using garbage collection, we generally don’t need to manage memory, but it's just one type of resource. Whenever we acquire a resource, we must ensure it gets released afterward. Unfortunately, many have seen or written C code that looks like this, where improper resource management can lead to memory leaks.
00:12:16.399
In a simplified example, I created a function `foo` that acquires and releases a resource while conducting work in-between. This code is problematic because if an early return occurs or an exception gets raised, resources may not be released properly, potentially causing memory leaks.
00:12:43.759
We can work around this issue in C++ with an affordance called "deterministic destruction semantics." In C++, when a local variable goes out of scope, its destructor guarantees that resources are cleaned up immediately.
00:13:02.080
This feature enables you to create a wrapper class that ensures resources are acquired and released correctly. I can show how this is implemented using a class called `SafeResource`, which acquires resources in its constructor and releases them in its destructor.
00:13:25.760
By employing this pattern in C++, I don’t need to explicitly release the resources. It guarantees that regardless of how I exit a function, the resource will be freed. Why? Because no matter how I exit—be it due to an error, exception, or normal termination—the destructor will always be executed, thus preventing resource leaks.
00:13:55.920
This leads us back to Ruby, which, unlike C++, does not have deterministic destructors. In Ruby, however, we can utilize `begin...ensure` blocks, ensuring resources are released in the ensure block.
00:14:21.440
You can also define finalizers on objects to execute when the garbage collector gets rid of the object. However, we can’t precisely predict when the garbage collector will run. So, to manage resources efficiently in Ruby, we can rely on blocks.
00:14:46.799
Here’s a simple example of `SafeResource` in Ruby that mirrors the previous implementation. The `initialize` method acquires the resource, and the `acquire` class method wraps interactions with the object in a block. When we exit the block, the resource will be released appropriately.
00:15:19.120
Another relevant example is `file.open` in Ruby, which takes a block. It opens a file for reading and ensures it closes when you’re done, exemplifying this valuable pattern.
00:15:49.280
Continuing, we can explore how to maintain familiarity with patterns that have deterministic destructors in Ruby, particularly by utilizing block structures. While this approach ensures resources are managed effectively, it requires that you change your programming pattern slightly.
00:16:20.560
Let's shift gears once again. I learned about the iteration of subclasses in Smalltalk, allowing us to handle dynamic responses without dealing with registries or case statements.
00:16:46.559
In Ruby, we can implement a similar approach to read different image types by utilizing classes and subclasses to establish a solid framework for extending functionality. Just define image reader classes and ensure that they are verified against the content of the stream.
00:17:10.559
For instance, I can create an `ImageReader` class that opens binary streams and checks if they can read a specific image file format. By following this pattern and utilizing inherited hooks, I can ensure all subclass readers get registered correctly.
00:17:39.239
This dynamic nature reflects how various languages and their affordances can aid in solving problems more effectively. As such, I can derive image readers like `BitmapImageReader`, `JPEGImageReader`, and adjust accordingly based on the first few bytes of the stream.
00:18:06.240
To summarize, hopefully, you’ve learned a few Ruby tricks that could enhance your programming experience. Remember that languages afford certain designs and inhibit others. In thinking within a particular programming language, you are often equipped to solve problems in unique ways, and this broadened perspective leads to greater creativity in your programming.
00:18:42.960
It is advisable to learn new languages, as you don’t need to write production-level code to benefit from them. Familiarizing yourself with the idioms and patterns of different programming languages can significantly improve the way you think about software design.
00:19:10.880
Thank you for your time today! I also want to thank my employer, Key Technology, for sending me to this conference and supporting my attendance. By the way, we're hiring if anyone is interested in working with Smalltalk and C++ along with a growing Ruby codebase.
00:19:36.960
A special thanks to the Rogue Ruby Users Group for facilitating practice sessions which helped refine this talk, as well as to Jen Myers, who provides impactful mentoring for beginner speakers. If you would like to explore the topic further, I have detailed blog posts discussing these concepts. Visit the provided link for more resources.
00:20:01.760
I appreciate your attention today, and I hope these discussions on affordances and programming languages prove beneficial for your future work.