4 ms·
My 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 u
by travisgriggs 5y ago
My 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.