Functional programming patterns in Smalltalk
blog.wilcoxd.com
blog.wilcoxd.com
I often find it amusing that it’s a sort of an extension in something like Kotlin, but you struggle to go 10 characters in Smalltalk without crossing a closure.
Because closures were first class polymorphic objects (as opposed to compilers-only-language-feature), you could build your own function composition patterns.
Want to lazy memoize a closure? You could add a “once” method to closure to do that. You could extend Symbol with value: and use #symbols for shortcut closure syntax for unset sends. Want trinary logic? Easy. Optionals with lazy alternatives. No problem.
And many of the standard systems did add these as part of the system. There was no deep sauce. Easy to see and grok what was going on.
Richard Gabriel once mused over lunch at OOPSLA that it was ironic that Lisp had built the ultimate meta object system (CLOS) which should have been Smalltalks tour de force it being a testament to object/message all the way down, but that Smalltalk block closures had made the lambda (one of Lisp’s pillars) so simply and widely useful in a language that wasn’t Lisp.
Actually, looking at it's design history[2] it took a lot of inspiration from a lot of languages before ending up where it is now (or maybe "where it's taking a break for now" is a better way of putting it). So it's no surprise that Van Oortmerssen tried to keep the best bits of every language he visited.
[0] https://strlen.com/lobster/
[1] https://aardappel.github.io/lobster/language_reference.html (scroll down to function calls)
I implemented different versions of Smalltalk VMs and conducted measurements (see e.g. https://github.com/rochus-keller/som/#concluding-experiments). The same benchmark written in plain Lua compared to the SOM version (with just standard inlining) is more than factor seven faster.
How does Strongtalk fare, for example?
(NB: Just asking. I'm actually not a fan of closure-heavy code. I tend to root them out during code review.)
I didn't compare with Strongtalk, but with Pharo 7 and 8, which use the Open Smalltalk VM (i.e. Cog/Spur) that is the result of a multi-decade performance optimization effort by Eliot Miranda and team. As you can see in my results Pharo shows essentially the same performance as the plain Lua version on LuaJIT (the tracing version 2.0 of it took "only" seven years to develop in comparison). Here again the link to the report for convenience: http://software.rochus-keller.info/are-we-fast-yet_crystal_l....
My first hire for a project written in Lua had Smalltalk experience, and the way we extend Lua's metaobject protocol is heavily inspired by that and Ruby.
But anything in Lua is ultimately built around tables, coroutines, and plain-old-closures; Lua chunks are in fact anonymous functions but they are very fast and easy to compile since chunks can't be moved. It does mean tolerating statement-orientation and built-in control flow; I can live with that.
Ruby handles blocks in a way inspired by Smalltalk, which seems to me similar, but I've never used smalltalk. Do you have any experience with ruby to say where you think it falls short compared to Smalltalk?
If you're interested in experience what a modern Smalltalk looks like, I strongly recommend the Pharo programming MOOC. It was a eye opener for me: https://mooc.pharo.org/
I was curious specifically, though, about "a language that did functional closures so elegantly, uniformly, consistently, and ubiquitously as Smalltalk" -- does ruby, with it's smalltalk-inspired blocks, used heavily in stdlib and idiomatic ruby code? If not, where does it fall short in the opinion of a smalltalker?
Unless the environment/tooling is part of this?
Smalltalk was certainly a pioneer in that area but in the past twenty years, plenty of languages have implemented closures in a way that's pretty much 100% identical to how Smalltalk did it, just with different syntaxes.
What's so different about Smalltalk's closures from the likes of Rust, C#, Kotlin, Swift, Groovy, etc...?
a = b ifTrue:[ ... ]
a = b ifFalse:[ ... ]
a = b ifTrue:[ ...] ifFalse:[ ... ]
[ a = b ] whileTrue: [ ... ]
And "control structures" aren't actually control structures that are built into the language. They are just messages sent to blocks (closures). That includes iteration, so a for "loop" is: 1 to:10 do: [ :i | ... ]
Which is just a shorthand for (1 to:10) do:[ :i | ... ]
With (1 to:10) creating an Interval object, a virtual collection that understands the normal collection iteration protocol (do:, collect:, select:, reject:, inject: ).OO + Closures + keyword syntax. An elegant and powerful combination.
In fact, continuing along these lines, it occurs to me that many systems seem to tend towards a similar design: functional programming at the small scale, object-oriented patterns at a larger scale. Quite aside from Smalltalk, this also arises in Haskell, with various effect systems (e.g. MTL) providing the statefulness, encapsulation and late-binding which in other languages is encoded in dedicated OOP syntax. On the other hand, I have less experience outside Haskell (I am but a mere hobbyist programmer), so I don’t really know how well this theory holds up elsewhere.
I also don't think you are wrong that Smalltalk tends to be more functional than many other languages that call themselves "object oriented", which tend to emphasize a more procedural and imperative style (e.g. use of for loops instead of functions like map) of programming functions/methods.
And your big picture OO-ish, small scale functional observation does sound about right, at least for languages that are more functionally aligned. Erlang uses its actor pattern mostly for the large scale organization, with details generally being pretty normal functional code.
Even in procedural languages there is a growing tendency to add features to make functional style programming easier, and growing recognition that immutable data is easier to reason about, etc.
ourList := { 1. 2. 3 }.
ourList collect: [:in | in + 1 ].
Here it is in Kotlin: val list = listOf(1, 2, 3)
list.map { it + 1 }
No spurious colon, dots sprinkled a bit everywhere, mysterious ":in" or pipe bar, square brackets. Just a very naked syntax.Next:
ourList select: [ :num | (num \\ 2) = 0 ] thenCollect: [:n | n + 2]
Kotlin: list.filter { it % 2 == 0}.map { it + 2 }
The next example is an ever better illustration of why Smalltalk feels so antiquated and wasteful, and is not respecting the developer's time: ourList chain
select: [ :num | (num \\ 2) = 0 ];
collect: [:n | n + 2];
collect: [:m | m + 2].
Why do I have to find names for all these closure parameters? And why use a different name every time (num, n, m).Again, Kotlin shows how to do it right:
list.filter { it % 2 == 0}.map { it + 2 }.map { it + 3 }There's nothing inherently more quirky about separating list elements with `.` then with `,`, until you've gotten used to a cross-language convention of using `,`. Which Smalltalk predates.
There's nothing inherently more "naked" or less mysterious about using square brackets to delineate a block/lambda instead of curly brackets... and I'm not even sure on what basis curly braces seem more natural to you! What makes curly braces more obvious than square brackets?
Why do you have to find names for closure arguments? I don't know why do you have to find names for arguments in functions/methods either? One could imagine a language (perhaps untyped, to keep things consistent) where you didn't, perhaps any one-argument function would just have an argument named `it`. Heck, maybe a two argument function would just have arguments named `it` and `other`. We could argue about whether that was a good idea or not... but I'm unpersuaded that it's more "naked", as if there is some kind of natural correct one true syntax out there waiting to be discovered. I get that you like Kotlin; I think you are mistaken to think it is objectively more "advanced", and am confident we could find people who don't like some of it's choices.
You are missing the point.
What Kotlin gets right here is that most of the time, these closures only have one parameter so it allows you to not declare that parameter and instead, use `it`. Smalltalk (and many other languages) missed that simple quality of life improvement, but once you get used to it, it really makes the code more readable. Obviously, you can always decide to pick your own name anyway, so Kotlin's approach subsumes Smalltalk's here.
> I think Smalltalk seems "quirky" compared to your Kotlin example only becuase we've all gotten used to a standardized C-inspired syntax.
My examples are not complaining that Smalltalk uses [] instead of {}, please reread what I wrote. Smalltalk is making a lot of bizarre syntactic choices that really only impede readability with unnecessary symbols (e.g. the . terminator).
OK:
> No spurious colon, dots sprinkled a bit everywhere, mysterious ":in" or pipe bar, square brackets. Just a very naked syntax.
You seemed to be complaining about the square brackets, did I misread? Why were you mentioning the square brackets?
My point was that there is nothing inherently "mysterious" about any of those things, except I guess if you're used to the cross-language conventions that have developed since smalltalk, which is fair (and except I don't think curly vs square brackets for blocks/closures is even one of those).
But I wasn't trying to be rude or read you incorrectly. I suspect this conversation is going nowhere, that's fine, good day!
It's called Kotlin.
Of course this is a relatively minor feature and few C-like or ML-like languages have it. Using it against Smalltalk is a bit unfair.
I'm obviously not familiar with kotlin.
I'd call it "quirky" to suggest that it's more "advanced" to automatically name arguments `it` for all function definitions!
I thought the kotlin enthusiasts were only suggesting it for anonymous inline blocks/closures, but was hypothesizing, ok so would doing it for all functions be even more "advanced"? (Is the answer that kotlin already thinks yes?!)
The "listOF()" function (is it a function? Or special syntax?) certainly looks a lot worse than actual literal syntax, though I'd agree that Smalltalk 80's literal syntax is not ideal.
In Objective-S, you can still use your own block parameters, but any non-named parameters will be assigned numbered $ names.
list ← #( 1,2,3 ).
list collect:{ $1 + 1 }.
Of course, simple maps don't really require the syntactic overhead of a closure, a Higher Order Message is much better suited: list collect + 1.
Next example list select: { ($1 mod: 2) = 0 } | collect + 2.
Keyword syntax allows "operator-like" named methods. ourList select: { ($1 mod: 2) = 0 } | collect + 2 | collect + 3.The beauty of Smalltalk is that these snippets show 90% of all the Smalltalk syntax. There's not much else, really! It might not look like a modern mainstream language, since all of them feature C-style syntax. But it's still worth a look: learning it can be done in a single afternoon, and you would realize all the things you haven't realized yet about how clever that small syntax set is.
Kotlin's lambda syntax is actually similarly verbose. But they allow you to use a default argument called it.
list.map { it+1 }
is equivalent to list.map { oldValue -> oldValue+1 }
Still readable and you can easily just rename the it variable in your IDE to save a bit on typing. There are many examples in the language where little time saving syntactic sugar just helps the programmer having to type a lot. Kotlin internal DSLs are really nice once you wrap your head around how to create them. That's a trick they picked up from the ruby community.However, I do also appreciate just how far ahead of its time Smalltalk was. I played a bit with it in the nineties while in university around the time Java was released and had no IDE whatsoever. Refactoring, which is a super power now granted to those using Java or Kotlin and in much smaller quantities to users of other languages was actually invented in the smalltalk community. They had IDEs with refactoring built in as early as the late nineties. They pioneered a lot of good stuff. Sadly, the tool chain was a bit weird to deal with because another of their innovations was storing the program code in a database rather than a lot of text files. That actually facilitated a lot of the stuff that you could do with smalltalk. That's something that never caught on but wasn't necessarily a bad idea. I think Visual Age had this briefly for Java and then got rid of that when it became eclipse.
A notable smalltalk dialect called self also existed. Together with Javascript, that's one of the few object oriented and protype based languages around. I think that was something that emerged out of Xerox and Sun in the eighties. Sun did some amazing things around that time. Such a shame that they ended up being gobbled up and destroyed by Oracle.
I think you could say that smalltalk is to object oriented programming what lisp and scheme are to functional programming. They are the purest realization of those paradigms but ultimately they have proven less popular than more practical languages that are maybe a bit less pure. Kotlin is a great example of a language that has enough of the OO and functional heritage to be useful for both. They definitely cut some corners here and there and there's plenty for a functional language purist to take offense at. But for day to day usage, it's a very nice language.
Pretty sure they picked it up from Groovy, which itself picked it up from Ruby and Smalltalk first.
Popularity is familiarity, not intuition. To my aesthetic taste the Kotlin looks hideous and the Smalltalk elegant and light; but then, I'm able to read them both.
list chain
select: [:i | i \\ 2 = 0 ];
collect: [:i | i + 2];
collect: [:i | i + 3].It isn't "intuition". It's familiarity from using some other language that used {} syntax.
> mysterious ":in"
Less mysterious as
list collect: [:each | each + 1 ].
or list collect: [:element | element + 1 ]. a, b, c, d pairsDo: [:first :second | first append: second]
> ab
> bc
> cd listOf("a", "b", "c", "d")
.chunked(2)
.map { (first, second) -> first + second }
Or simply: listOf("a", "b", "c", "d")
.chunked(2) { it[0] + it[1] }Also,
The compiler of a statically typed language can know that a lambda that takes one argument is passed to a function that calls it with one argument. This would be hard to guarantee in Smalltalk so you’d need a convention that finding an undeclared variable “it” in the block converts the block to a one-argument block.
Finally, I imagine nullary lambdas in Kotlin are less common than no-argument blocks in Smalltalk.
Yes, which is why it's recommended to name your closure parameter (instead of using `it`) when you nest these.
list.filter { it % 2 == 0}.map { it + 2 }
Does `list.filter { it % 2 == 0}` dynamically create a list or an iterator or what does it do?To avoid that you must use `list.asSequence().filter { .. }.map { .. }.toList()`.
Smalltalk's Collection classes have a method #species which return what class to use as the new container.