F-Strings for Pulsar (and Pharo)

F-strings are coming to Pharo. A new implementation brings readable string interpolation to Pulsar today, with proper AST support, debugger integration, and backward compatibility—and a path toward Pharo 15.

F-Strings for Pulsar (and Pharo)

In Pharo, as in Smalltalk in general, building a string with dynamic parts means gluing pieces together:

'Hello, ' , name , '! You have ' , unreadCount , ' unread messages.'

The constant text and the code that fills it in are hard to separate. You read the line hunting for commas, not sentences.

What is string interpolation?

String interpolation — often called f-strings, a naming we inherited (I think) from the Python community — lets you write expressions in the middle of a string literal. The result is an interpolated string: a mix of the string's constant part and its runtime-evaluated part.

That same message becomes:

f'Hello, [name]! You have [unreadCount] unread messages.'

Honestly, for a string this small there is no clear winner between the two forms. But look at a bigger one — a template:

" before "
'HTTP/1.1 ' , statusCode , ' ' , statusText , crlf,
'Content-Type: ' , contentType , crlf,
'Content-Length: ' , contentLength , crlf,
crlf,
body

" after "
f'HTTP/1.1 [statusCode] [statusText]
Content-Type: [contentType]
Content-Length: [contentLength]

[body]'

Doesn't the template read more clearly than its construction does?

And this is where f-strings biggest strength lies. Since web programming became the norm, this kind of string — a template with a few holes — appears everywhere in the code we read and write: HTTP headers, HTML fragments, log lines, error messages. Interpolation is not a convenience for toy examples; it matches the shape of most strings in real application code.

A long conversation

The community has been discussing string interpolation for a long time, as a way to improve the language in a way that is actually useful to our users. We even have a Pharo Enhancement Proposal (PHEP) that has been around for some time. I was the one who proposed it, but that's not relevant — it was the result of years of conversation within the community.

Tired of not seeing movement on the proposal (as proponent, and by the PHEP rules, pushing it was my responsibility 😉), and tired of seeing awkwardly formed strings all around my code, I wrote a new implementation of f-strings last month.

How the new implementation works

The new implementation has an important difference from the original proposal. Instead of making every string in the system interpolating, it introduces a new kind of string, with a few deliberate characteristics:

  • It reproduces Python's syntax, partially (f'... [statement] ...'). The reason is twofold: why reinvent a syntax when there is already a well established one? — and it is a wink to Python users who may want to give Pharo a try one day.
  • The statements are enclosed in [], not {}, unlike Python. This is deliberate: when you build the mental map of what a piece of code does, in Pharo the interpolation sits closer to a block ([]) than to an array ({}).
  • It generates a real AST node. Prefixing a string with f produces an OCFStringNode, carrying all the information the tooling needs — for example, to place debugger stop points correctly, so you can step into the internal statements.
  • It is backward compatible. Regular strings are parsed exactly as they always were; only strings prefixed with f go through the new parser. So introducing square brackets into an existing string literal cannot break your code.

Try it

F-strings are already included in Pulsar, and after passing its validation phase there, they will be upstreamed to Pharo 15.