7 ms·
Functional programming patterns in Smalltalk
- travisgriggs 5y agoMy post-Smalltalk journeys of the last 10 years have yet to take me across a language that did functional closures so elegantly, uniformly, consistently, and ubiquitously as Smalltalk. 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.
- hota_mazi 5y agoCan you elaborate? 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...?
- pjmlp 5y agoThey are objects as well, you can get the metaclass of a Smalltalk code block and manipulate it at your pleasure.
- mpweiher 5y agoOne difference is that they are used for all control structures: 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.
- afandian 5y agoIt's been a while but this looks very much like Ruby to me.
- projektfu 5y agoNot “part of the language” but definitely privileged in the implementation. #ifTrue:ifFalse: gets rewritten and inlined as a branch in just about every compiler. If you implemented your own, it would suffer in performance.
- Rochus 5y agoSmalltalk blocks are a very elegant concept, but also expensive (i.e. a major reason for the comparatively low performance); all loops and conditions are essentially closure calls and inlining is difficult. I implemented different versions of Smalltalk VMs and conducted measurements (see e.g. https://github.com/rochus-keller/som/#concluding-experiments 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.
- cxr 5y agoIs this a valid comparison? The S in SOM isn't supposed to stand for "speed". (The real S is arguably adversarial to that goal.) 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.)
- samatman 5y agoI would say it's absolutely a valid comparison, although Lua doesn't encourage the object-messages style of programming, it cooperates excellently with it. 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.
- Rochus 5y agoI compared implementations of the benchmark which all run on the same LuaJIT VM. One implementation is just plain Lua using the loops and control flows provided by the language. Another implementation is written in SOM and transpiled to Lua; all SOM (i.e. Smalltalk) closures are faithfully translated to Lua closures (i.e. not using Lua loops and control flow). Yet another implementation does the same but directly LuaJIT bytecode is generated instead of Lua source code which enables some additional optimizations. The performance difference is very likely caused by the number of closures in use (it's a bit more complex than that, but the principle is the same). 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_lua_node_som_pharo_i386_results_2020-12-29.pdf http://software.rochus-keller.info/are-we-fast-yet_crystal_l....
- vanderZwan 5y agoHave you tried lobster[0]? I think it took a lot of inspiration from Smalltalk in how it handles closures and control structures, in that it tries to make function call syntax work like control structures[1]. 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/ https://strlen.com/lobster/ [1] https://aardappel.github.io/lobster/language_reference.html https://aardappel.github.io/lobster/language_reference.html (scroll down to function calls) [2] https://aardappel.github.io/lobster/philosophy.html https://aardappel.github.io/lobster/philosophy.html
- jrochkind1 5y ago> take me across a language that did functional closures so elegantly, uniformly, consistently, and ubiquitously as Smalltalk. 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?
- Qem 5y agoI think the most distinguishing feature is the superb environment and tooling that is a integral part of the language, by default, in most Smalltalk dialects. The tooling is so seamless integrated, that you're nudged towards simply code inside the debugger. Things like search method by example feel like a superpower. See https://m.youtube.com/watch?v=HOuZyOKa91o https://m.youtube.com/watch?v=HOuZyOKa91o 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/ https://mooc.pharo.org/
- jrochkind1 5y agoThanks, I have heard that! 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?
- Qem 5y agoI'm not sure. I have little familiarity with Ruby, writing my code mostly in Python. But on uniformity, consistency, and ubiquity, at least when compared to Python, I think Smalltalk has the advantage of being implemented in itself, all the way down to the VM. So, once you learn the the language, it's straightforward to study the implementation details of anything inside the system image. On the other hand, when deep-diving in Python, its hard to avoid smashing your head against the C bedrock underneath, as many parts of the language and important modules are just wrappers around C code. I think Ruby may be similar to Python here.
- hota_mazi 5y agoI think the author is so enamored with Smalltalk that they no longer see how quirky and non intuitive the Smalltalk syntax is. Case in point: 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 }
- samatman 5y agoThere is absolutely nothing whatsoever intuitive about the C flavor of Algol syntax. 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.
- pjmlp 5y agoBeauty is on the eyes of the beholder.
- VaxWithSex 5y agoHow does `it` look with two parameters? for example when you have #pairsDo: ? a, b, c, d pairsDo: [:first :second | first append: second] > ab > bc > cd
- maybe_pablo 5y agoThis would be: 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] }
- 5y ago
- bradrn 5y agoI’ve long thought that Smalltalk is as much a functional programming language as an object-oriented one, if not more so. From what I’ve seen, idiomatic Smalltalk code is constructed largely using anonymous functions, higher-order functions, function chaining, and other forms of function composition — hallmarks of more traditional functional languages. The object-oriented nature of Smalltalk becomes most apparent when looking at higher-level architecture, where it becomes obvious that programs are constructed of interacting things using each others’ methods. 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.
- jsmith45 5y agoI think to the extent that that a program needs interactivity whether via network, or some form of user interface, it is pretty normal to see something at least vaugely OOPish if you squint right in the overall program structure. This can be (among others): an actor system, an effect system, a struct full of function pointers approach, or native OOP support. 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.
- deleted 5y ago[deleted]