> The unifying aspect of new languages such as Rust, Nim, and Gleam is that they were designed from the beginning to be beyond paradigms.
I really don’t think this is correct at all. Rather, Rust/Nim/Gleam are first and foremost imperative languages. They may have some functional and Lispy features thrown in, but that doesn’t change the fact that programs in those languages involve writing statements to be executed one after another — the defining aspect of imperative languages.
If you really wanted to, I guess you could define this particular combination of ‘imperative+functional+macros’ as its own new paradigm. These languages are consistent enough with their design that that might actually make sense. But it’s certainly not ‘post-paradigm‘ in any meaningful way.
Agreeing and amplifying: I believe multi-paradigm is a useful term, but you can always find a prioritization in the paradigms. Your language is going to privilege either mutable or immutable data. It can support both, but one is going to be considered the default. Even if the language itself doesn't, the standard library and the resulting influence it has on the 3rd party libraries will result in a preference. Your language will privilege one side or the other of the expression problem. Your language will privilege statements or functions. Your language will privilege static or dynamic types. Your language will prioritize compile time things or run time things. And so on for quite a few things. Even when someone finds an interesting way to split the difference in one of those categories, that does not defy this categorization, it adds a new one, a new way of prioritizing. But that new way still won't be a completely even 50% split.
There are many multiparadigm languages. They are even the norm now. But being multiparadigm doesn't mean they all support all paradigms equally. Programming X in Y is a problem for almost all combinations of X and Y for X != Y, because the language will always have a "grain" to it. Thus, it is still meaningful to argue about which paradigm preference is suitable to which tasks.
Even two of the most similar languages there are, at least in terms of language spec, Python and Ruby, demonstrate significant differences in the orientation of the library ecosystem in some of their philosophies.
Mostly agree but Common Lisp goes out of its way to not prioritize paradigms.
Immutable or mutable? Depends how you write it. Lists don't mutate until you choose to.
Statements or functions? Both I guess. Loop is declarative, many things are imperative, some things are functional.
Dynamic or static typing? Depends whether you bothered to give the compiler type hints or not.
Compile time or runtime? CL is AOT by default but you can eval-when your way into running code whenever you want. Readtime too
To be honest one of the biggest problems with CL is the multiparadigmatic design - you end up with many styles of code, all of which are valid and none of which anyone can agree on.
I agree with all of this. But I’ll point out that ‘the paradigms can be prioritised’ wasn’t intended to be my main point there. I wanted rather to refute the argument that Rust etc. aren’t ‘post-paradigm’ at all: on the contrary, they fit in quite well with existing paradigms.
> Even two of the most similar languages there are, at least in terms of language spec, Python and Ruby, demonstrate significant differences in the orientation of the library ecosystem in some of their philosophies.
Hmm… I feel you can get a lot more similar than that. Python and Ruby are actually rather different to my mind. Rather, you could take OCaml vs SML, or Scheme vs Racket. The differences are still there, but it’s much more subtle.
To add to my parent comment, I think there’s another point to be made here: ‘post-paradigm’ is, to some extent, a contradiction in terms. You can’t build a language without its basic structure implying some paradigm (or possibly more than one).
There’s a perspective I find useful here. I tend to think of different ‘programming paradigms’ as different approaches to solving problems. In imperative languages, you solve problems by modifying values in sequence until you get to the answer; in functional languages, you solve problems by building up a function which gives you back the answer; and so on.
The key thing here is, you do need to give yourself some way of solving problems. That translates directly to the paradigm which your programming language adopts. If you give yourself more than one way of solving problems, then the language becomes multi-paradigm. In rare cases, you might even end up inventing a totally novel problem-solving approach, and hence a new paradigm… but even that’s not ‘post-paradigm’, it’s just another paradigm.
I don’t disagree on any of your technical points. But I also think for practical purposes you’re missing the forest here. I agree with the sentiment of the article - I think the big trend in general purpose PLs is a blend of multiple classical paradigms. Perhaps we’re moving the goalpost and paradigms need to be rearranged - but that is intrinsically interesting - it’s literally the continents of knowledge drifting slowly into new configurations.
Single-paradigm languages like prolog, CSS or SQL keep their restrictions not because the lack of use-cases, but because the benefit of keeping the complex execution engines away from the end-user exceeds the minor wins in expressiveness.
I don’t think it’s a coincidence that the declarative languages are in this category. They are higher level, and opening up low-level customizations is really tricky: for instance, if you put imperative code inside your CSS, it needs complex “re-evaluation rules”, that results in a dilemma: either give full control to the programmer, which imposes specific execution engine designs and complex API surfaces – or re-evaluate too often, which risks killing memoization and perf (cache invalidation). It could be even worse if the code has side-effects or dep cycles.
On the contrary, imperative low level languages like Rust can easily come along and say things like: “this is not only a function, but a side-effect free function”. “This is not just a reference, but an immutable reference”. Then you can cleverly leverage those traits in your “execution engine” ie the compiler, to deliver low-level perf. There are even people who describe rust as a high-level language for these reasons, which is a bit provocative to me but in all honesty not completely outrageous.
An alternative take on the last 10-15 years:
- General purpose PLs typically have an imperative base, while integrating multiple classical paradigms:
- Only a few aspects of OOP are added to modern PLs, where inheritance has largely been superseded by simpler composition
- Features from FP have surged in popularity, being integrated and even retro-fitted into general purpose PLs, providing both perf- and DX improvements
- Structured meta-programming and/or codegen has been a strong focus for compiled languages, acknowledging that it’s preferable to limit the complexity of the core language at the expense of separate pre-compile phases
I don't think Prolog is a single paradigm language. It just has one dominant paradigm: Logic Programming. But in actual Prolog systems you'll find procedural programming, constraint programming, object-oriented programming, meta programming (programming on the language level).
Once it was Borland's Turbo Prolog. Quote: "It combines the best features of logical, functional, and object-oriented programming paradigms, offering a powerful, type-safe, high-level language. Visual Prolog is well-suited for developing applications for Microsoft Windows 32/64 platforms and supports advanced client-server and three-tier solutions."
There is a comparison of Prolog implementations on Wikipedia:
I haven't researched this, but there should be zillions of language extensions like this to Prolog. It is one of the symbolic AI languages, as such it has a long history of extensions.
> I don’t disagree on any of your technical points. But I also think for practical purposes you’re missing the forest here. I agree with the sentiment of the article - I think the big trend in general purpose PLs is a blend of multiple classical paradigms.
You’re not wrong. That may even be the sentiment of the article. But the article certainly doesn’t phrase it that way — it’s saying that modern programming languages are ‘beyond paradigms’. That’s clearly wrong, and that’s what I’m arguing against.
Fair enough! I agree with that and looks like I missed your main point.
It’s a bit naive and generally ahistorical to think we’ve “transcended paradigms”, whether within tech or outside. It’s similar to the bias of thinking that current year/western/majority perspectives are “enlightened” and free of bias. Usually just means we’re unable to see the bigger picture.
Gleam is one of the really really functional languages out there. There is no mutability, there are no statements, no loops. It has let bindings, function application, and conditionals through if/case, and that’s it. It has just tricked you because it has a very nice and friendly syntax.
It also has no macros so I don’t know what you mean in that last paragraph.
"Functional" has lost its meaning. Originally, functional programming was rather well-defined - now everything that has a `.map` is called functional. Gleam goes a longer way than, say, Python. But since it still has side-effects and lacks a system, it is very different from e.g. Haskell.
If Gleam is imperative by your logic, then Scheme and the whole ML family (SML, OCaml) would be imperative as well. That's a strange choice of semantics. Even Haskell has unsafePerformIO, which it might sadly need.
Imperative and (pure) functional are not opposites! They are actually orthogonal.
The opposite of imperative is declarative, and the opposite of (pure) functional is non (pure) functional.
Schema and (to my limited knowldge) the ML family are in fact non (pure) functional. However, they share more functional-aspects with pure functional languages than most other popular languages do.
> Even Haskell has unsafePerformIO, which it might sadly need.
Sure, it's almost never a black and white thing. But I think for the sake of the argument we can call Haskell a pure functional language, even if it is indeed possible to write non pure functional code in it. There are actual pure functional languages (like Idris) but I think for practical use in the discussion it's fine to call Haskell pure functional. Scheme and ML languages are very different from Haskell though, so I think they deserve to be called differently. They are however also different from, say, C. So to improve our communication I think it is worth to not put those 3 things into 2 boxes. We need 3 boxes here.
Disclaimer: I want to emphasize that none of any of that says that a language is inherentially better or worse! This is just terminology-talk.
It isn't clear what you're discussing. You mentioned that functional programming has lost its meaning and then went on to describe things as if your defintion would exclude some of the most shining examples of functional programming.
Functional does not mean just pure functional. And a purely pure functional language would literally be worthless as it wouldn't be able to do anything useful.
> then went on to describe things as if your defintion would exclude some of the most shining examples of functional programming
Maybe those examples are "most shining" from your perspective or definition but not mine. Mind to mention the specific languages?
That being said, to be precise, functional programming is a technique or style, it's not a property of a language anyhow. However, some languages make it very hard or even impossible to apply the style, either fully or partially. So practically speaking, that is how I classify a language as more or less functional.
> Functional does not mean just pure functional.
Yes, that's exactly what I said in my first sentence no?
Originally "functional programming" had the meaning of what we nowadays often call "pure functional programming" though. Language and definitions change over time. I was ranting a bit that the term "functional" is nowadays so unclear that there is little benefit in knowing that a language is "functional".
> And a purely pure functional language would literally be worthless as it wouldn't be able to do anything useful.
This is wrong. You haven't understand what functional programming is. In a nutshell it means that you are not directly executing effects, but rather that you build up a datastructure that describes the effect-execution. You then pass around an modify that datastructure to the point where you return it as the last thing that your does in the main-method (or whatever your language/runtime calls that). From that point on, this datastructure is being processed and the effects (like writing to a file, showing something on the screen) are (most likely) executed by the runtime.
In other words, there is no restrictions on what you can do with a pure functional language. You can try it out with Idris, which is a 100% pure functional language without any escape hatches. And still you can make it do all kinds of effectful things like processing files, sending emails, ...
I guess I was confused, and still am, about what the point is here.
> You haven't understand what functional programming is.
I almost exclusively use functional languages, so I'm not sure about that.
> I was ranting a bit that the term "functional" is nowadays so unclear that there is little benefit in knowing that a language is "functional".
I'm actually not sure that is a problem. Any examples? And it's quite common to describe something as "functional-first" if it has a functional core but allows other paradigms such as imperative or OOP, such as OCaml.
And a lot of this is based upon the semantics and definitions of the words like "pure", "functional", etc. which are more like spectrums than binary.
> definitions of the words like "pure", "functional", etc. which are more like spectrums than binary.
That's exactly it. "functional programming" originally was not a spectrum. It was well defined. Functions meant "pure functions" or (which is the same) "mathematical functions" in that context. And they still do when people use the term "functional programming" in the original meaning. Though nowadays, I rather use "pure functional programming" since "functional programming" was taken over. ;-)
So on the other hand, can you provide a precise and meaningful definition for "functional" from your own perspective?
I suspect it is possible to make the same argument for the contrary position. Once we're talking about "some functional and Lispy features thrown in"; we must start to question what a paradigm is.
If I write a program, fundamentally I have a blob in my head that I'm reifying into formal logic. It doesn't make sense to talk about the blob as having a paradigm; but I'm not sure that it is useful to talk about the reification as having a "paradigm" either. The appropriate techniques to be used are on a functional-imperative spectrum based on how stateful the problem is.
This whole idea of programming paradigms I think is a mis-take of a more fundamental question of how to classify programming problems - we're looking at the shadow in Plato's cave and getting nonsense because we expect the problem to to take on aspects of the programming language. Which is not a strategy for achieving success. We should be classifying problems based on state, not programming languages based on paradigms.
Most of the experts have figured that out, they pick their programming language based on the problem space they want to deal in. But the paradigm paradigm doesn't help in making those decisions, so it's utility is low.
I disagree. In Rust, traits are used all over for dynamic dispatch, the defining feature of object-oriented programming. Moreover, all three languages support structural pattern matching (borrowed from functional programming) as a core control-structure.
Certainly all three languages can be used in a simple, imperative style. But that's not the only paradigm that can be used unlike in C or early versions of Python. Many programs in these languages look significantly different than just statements and procedures.
> In Rust, traits are used all over for dynamic dispatch, the defining feature of object-oriented programming.
In my experience, Rust traits are used much more for static dispatch `fn foo<T: Trait>(T)` than dynamic dispatch `fn foo(Box<dyn Trait>)`. This, together with its ADTs and lack of inheritance, gives it a very different feel from most "object oriented" languages.
Also, what counts as a "defining feature" depends greatly on who's defining it. Though dynamic dispatch is certainly up there on most lists.
> Many programs in these languages look significantly different than just statements and procedures.
Like I said, they’ve certainly adopted features from other paradigms… but the basic, underlying structure of programs in these languages is still statements, sequenced one after another. It’s not like Haskell or Scheme where nearly every operation is ultimately done through function calls. And it’s certainly not ‘post-paradigm’ as the article claims.
> Moreover, all three languages support structural pattern matching (borrowed from functional programming) as a core control-structure.
As for this: people often associate functional programming with pattern-matching, but I’ve never really understood why. The only relationship is that it originated in the ML language family, which also happens to be functional. There’s many functional languages which lack pattern-matching (e.g. most Lisps), and there’s many non-functional languages which have it (Java, C#, Python).
I would think pattern matching as coming from string processing (https://en.wikipedia.org/wiki/COMIT , SNOBOL, ...) and pattern-directed programming coming from rule-based & logic programming (PLANNER, Prolog, ...).
Bro, Java and python just got it. Lisps don't include it because it's just another lib/extension. Even emacs had one (see the recent pcase lawn article)
Yeah, I'm having trouble seeing how Rust/Nim/Gleam can be written in a logic programming style (like Prolog or Datalog).
At minimum, that means there are _two_ paradigms: logic programming and "beyond paradigms"....which sorta means that we're not really beyond paradigms now, are we?
I think the reality is that certain previously-niche paradigms became cool again, and folks started grabbing off some of the most-visible pieces of those paradigms (pattern-matching, ADTs, map/reduce/filter, maybe even a pipeline operator) and adapting them to other paradigms.
I really don’t think this is correct at all. Rather, Rust/Nim/Gleam are first and foremost imperative languages. They may have some functional and Lispy features thrown in, but that doesn’t change the fact that programs in those languages involve writing statements to be executed one after another — the defining aspect of imperative languages.
If you really wanted to, I guess you could define this particular combination of ‘imperative+functional+macros’ as its own new paradigm. These languages are consistent enough with their design that that might actually make sense. But it’s certainly not ‘post-paradigm‘ in any meaningful way.