Posted by azhenley 3 days ago
Rust mentioned!
Then, suddenly, the enlightenment
The brain is essentially dreaming it’s escaped while the body is still programming in Java.
I'm sure others have written about this, but these days I think good code is code which has a very small area of variability. E.g code which returns a value in a single place as a single type, has a very limited number of params (also of single type), and has no mutable variables. If you can break code into chunks of highly predictable logic like this it's so so much easier to reason about your code and prevent bugs.
Whenever I see methods with 5+ params and several return statements I can almost guarantee there will be subtle bugs.
I don't like all scripting languages, but Python, for example, has a simple syntax and is easy and fast to learn and write. I think it also has a good standard library. Some things can be simpler because of the missing guardrails. But that's also the weakness and I wouldn't use it for large and complex software. You have to be more mindful to keep a good style because of the freedoms.
C++ and Rust are at the opposite end. Verbose to write and more difficult syntax. More stuff to learn. But in the end, that's the cost for better guarantees on correctness. But again, they don't fit all use cases as well as scripting languages.
I've experienced the good and the bad in both kind of languages. There are tradeoffs (what a surprise) and it's probably also subjective what kind of language one prefers. I think my _current_ favorites are Rust, C# and Python.
I don't see why that's a problem. If a function implements an algorithm with several parameters (e.g. a formula with multiple variables), those values have to be passed somehow. Does it make a difference if they're in a configuration object or as distinct parameters?
Early returns at the very top for things like None if you pass in an Option type don't increase the risk of bugs, but if you have a return nested somewhere in the middle it makes it easier to either write bugs up front or especially have bugs created during a refactor. I certainly have had cases where returns in the middle of a beefy function caused me headaches when trying to add functionality.
- this makes them really stand out, much easier to track the mutation visually,
- the underscore effect is intrusive just-enough to nudge you to think twice when adding a new `var`.
Nothing like diving into a >3k lines PR peppered with underscores.
I know it's irrelevant to his point, and it's not true of C, and it doesn't have the meaning he wants, but the pedant in me is screaming and I'm surprised it hasn't been said in the comments:
In C++ mutable is a keyword.
It's funny how functional programming is slowly becoming the best practice for modern code (pure functions, no side-effects), yet functional programming languages are still considered fringe tech for some reason.
If you want a language where const is the default and mutable is a keyword, try F# for starters. I switched and never looked back.
As a practical observation, I think it was easier to close this gap by adding substantial functional capabilities to imperative languages than the other way around. Historically, functional language communities were much more precious about the purity of their functional-ness than imperative languages were about their imperative-ness.
That's why F# is so great.
Functional is default, but mutable is quite easy to do with a few mutable typedefs.
The containers are immutable, but nobody stops you from using the vanilla mutable containers from .net
It's such a beautiful, pragmatic language.
You can program F# just like you would Python, and it will be more readable, concise, performant, and correct.
Also, the STM monad is the most carefree way of dealing with concurrency I have found.
For some reason, this makes me think of SVG's foreignObject tag that gives a well-defined way to add elements into an SVG document from an arbitrary XML namespace. Display a customer invoice in there, or maybe a Wayland protocol. The sky's the limit!
On the other hand, HTML had so many loose SVG tags scattered around the web that browsers made a special case in the parser to cover them without needing a namespace.
And we all know how that played out.
Posted from an xhtml foreignObject on my SVGphone
Isn't this a strawman? Even Haskell has unsafePerformIO and Debug.Trace etc. It just also provides enough ergonomics that you don't need them so often, and they "light up" when you see them, which is what we want: to know when we're mutating.
Rust is also like this (let x = 5; / let mut x = 5;).
Or you can also use javascript, typescript and zig like this. Just default to declaring variables with const instead of let / var.
Or swift, which has let (const) vs var (mutable).
FP got there first, but you don't need to use F# to have variables default to constants. Just use almost any language newer than C++.
In case anyone here hasn’t seen it, here’s his famous essay, Functional Programming in C++:
http://sevangelatos.com/john-carmack-on/
He addresses your point:
> I do believe that there is real value in pursuing functional programming, but it would be irresponsible to exhort everyone to abandon their C++ compilers and start coding in Lisp, Haskell, or, to be blunt, any other fringe language. To the eternal chagrin of language designers, there are plenty of externalities that can overwhelm the benefits of a language, and game development has more than most fields.
I think that's why we're seeing a lot of what you're describing. E.g. with Rust you end up writing mostly functional code with a bit of imperative mixed in.
Additional, most software is not pure (human input, disk, network, etc), so a pure first approach ends up being weird for many people.
At least based on my experience.
Nonetheless, I also heavily dislike non-alphabetical, library-defined symbols (with the exception of math operators), but this is a cheap argument and I don't think this is the primary reason FPs are not more prevalent.
Maybe they're right about the syntax too though? :)
(< lower-bound x-coordinate upper-bound)
Instead of having to do the awful ampersand dance. lower_bound < x_coordinate < upper_bound
except that usually you want lower_bound <= x_coordinate < upper_bound
which is also legal.In Python this is a magical special case, but in Icon/Unicon it falls out somewhat naturally from the language semantics; comparisons don't return Boolean values, but rather fail (returning no value) or succeed (returning a usually unused value which is chosen to be, IIRC, the right-hand operand).
And in SQL you have
`x-coordinate` between `lower-bound` - 1 and `upper-bound` + 1
which is obviously the best possible syntax.If you have a course where one day you're supposed to do Haskell and another Erlang, and another LISP, and another Prolog, and there's only one exercise in each language, then you're obviously going to have to waste a lot of time on syntax, but that's never a situation you encounter while actually programming.
I write way more algol-derived language code than ML, yet piped operations being an option over either constant dot operators where every function returns the answer or itself depending on the context or inside out code is beautiful in a way that I've never felt about my C#/C++/etc code. And even Lisp can do that style with arrow macros that exist in at least CL and Clojure (dunno about Racket/other schemes).
That increases the activation energy, I guess, for people who have spent their whole programming life inside the algol-derived syntax set of languages, but that’s a box worth getting out of independently of the value of functional programming.
At least for me, this was solved by Gleam. The syntax is pretty compact, and, from my experience, the language is easily readable for anyone used to C/C++/TypeScript and even Java.
The pure approach may be a bit off-putting at first, but the apprehensions usually disappear after a first big refactoring in the language. With the type system and Gleam's helpful compiler, any significant changes are mostly a breeze, and the code works as expected once it compiles.
There are escape hatches to TypeScript/JavaScript and Erlang when necessary. But yeah, this does not really solve the impure edges many people may cut themselves on.
C# is not that far I suppose from what I want
e.g. Just a very simple example to illustrate the point
if (customer != null)
{
customer.Order = GetCurrentOrder();
}
vs if (customer is not null)
{
customer.Order = GetCurrentOrder();
}
Is there really any benefit in adding "is/is not"? I would argue no. So I categorise that as being of "dubious benefit" and there are many similar small features, keywords etc. that get added each release where they might save a few lines of code somewhere but I've never seem them used that often.if (obj is string s) { ... }
if (date is { Month: 10, Day: <=7, DayOfWeek: DayOfWeek.Friday }) { ... }
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref... https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
Microsoft give the same example though. I understand what hes saying, theres conceptual overlap between is and ==. Many ways to do the same thing.
Why couldnt it just be...
if (obj == string s) { ... }
> Would you say that these samples show no benefit to using the "is" operator?
I didn't say no benefit. I said dubious benefit.
I didn't really want to get into discussing specific operators, but lets just use your date example:
if (date is { Month: 10, Day: <=7, DayOfWeek: DayOfWeek.Friday }) { ... }
This following would do the same thing before the is operator: static bool IsFirstFridayOfOctober(DateTime date)
{
return date.Month == 10
&& date.Day <= 7
&& date.DayOfWeek == DayOfWeek.Friday;
}
And then: if IsFirstFridayOfOctober(date) {
...
}
I understand it is more verbose. But do we really need a new operator for this? I was getting on fine without it.Each release there seems to be more of these language features and half the time I have a hard time remembering that they even exist.
Each time I meet with other .NET developers either virtually or in Person they all seem to be salivating over this stuff and I feel like I've walked in on some sort of cult meeting.
It isn't any one language feature it is the mentality of both the developer community and Microsoft.
> I agree that they should not add new stuff lightly
It seems though kinda do though. I am not the first person to complain that they add syntactic sugar that doesn't really benefit anything.
e.g. https://devclass.com/2024/04/26/new-c-12-feature-proves-cont...
I have a raft of other complaints outside of language features. Some of these are to do with the community itself which only recognise something existing when Microsoft has officially blessed it, it is like everyone has received the official permission to talk about a feature. Hot Reload was disabled in .NET 6 IIRC for dubious reasons.
My bet is functional programming will become more and more prevalent as people figure out how to get AI-assisted coding to work reliably. For the very reasons you stated, functional principles make the code modular and easy to reason about, which works very well for LLMs.
However, precisely because functional programming languages are less popular and hence under-represented in the training data, AI might not work well with them and they will probably continue to remain fringe.
Also, category theorists think how excited people get about using the word monad but then most don’t learn any other similar patterns (except maybe functors) is super cringe. And I agree.
I once mentioned both these concepts to a room of C# developers. Two of them were senior to me and it was a blank expression from pretty much everyone.
> yet functional programming languages are still considered fringe tech for some reason.
You can use the same concepts in non-functional programming languages without having to buy into all the other gumpf around functional programming languages. Also other programming languages have imported functional concepts either into the language itself or into the standard libraries.
Past that. It is very rare to be able to get a job using them. The number of F# jobs I've seen advertised over the last decade, I could count on one hand.
dropping down into the familiar or the simple or the dumb is so innately necessary in the building process. many things meant to be "pure" tend to also be restrictive in that regard.
This creates friction between casual stakeholder models of a mutable world, versus the the assumptions an immutable/mostly-pure language imposes. When the customer describes what they need, that might be pretty close to a plain loop with a variable that increments sometimes and which can terminate early. In contrast, it maps less-cleanly to a pure-functional world, if I'm lucky there will at least be a reduce-while utility function, so I don't need to make all my own recursive plumbing.
So immutability and pure-functions are like locking down a design or optimizing--it's not great if you're doing it prematurely. I think that's why starting with mutable stuff and then selectively immutable'ing things is popular.
Come to think of it, something similar can be said about weak/strong typing. However the impact of having too-strong typing seems easier to handle with refactoring tools, versus the problem of being too-functional.
I believe Scala was pretty ahead here by building the language around local mutability with a general preference for immutable APIs, and I think this same philosophy shows up pretty strongly in Rust, aided by the borrow checker that sort of makes this locality compiler-enforced (also, interior mutability)
In particular: My brain, my computing hardware, and my problems I solve with computers all feel like a better match for time-domain-focused programming.
Haskell is a great example here. Last time I tried to learn it, going on the IRC channel or looking up books it was nothing but a flood of "Oh, don't do that, that's not a good way to do things." It seemed like nothing was really settled and everything was just a little broken.
I mean, Haskell has like what, 2, 3, 4? Major build systems and package repositories? It's a quagmire.
Lisp is also a huge train wreck that way. One does not simply "learn lisp" There's like 20+ different lisp like languages.
The one other thing I'd say is a problem that, especially for typed functional languages, they simply have too many capabilities and features which makes it hard to understand the whole language or how to fit it together. That isn't helped by the fact that some programmers love programming the type system rather than the language itself. Like, cool, my `SuperType` type alias can augment an integer or a record and knows how to add the string `two` to `one` to produce `three` but it's also an impossible to grok program crammed into 800 characters on one line.
Lisp is not a language, but a descriptor for a family of languages. Most Lisps are not functional in the modern sense either.
Similarly, there are functional C-like languages, but not all C-likes are functional, and "learn c-likes" is vague the same way "learn lisp" is.
Emacs lisp and Clojure are about as similar as Java and Rust. The shared heritage is apparent but the experience of actually using them is wildly different.
Btw, if someone wants to try a lisp that is quite functional in the modern sense (though not pure), Clojure is a great choice.
Don't know when was the last time you've used Haskell, but the ecosystem is mainly focused on Cabal as the build tool and Hackage as the official package repository. If you've used Rust:
- rustup -> ghcup - cargo -> cabal - crates.io -> hackage - rustc -> ghc
ghcup didn't exist, AFAIK. Cabal was around but I think there was a different ecosystem that was more popular at the time (Started with an S, scaffold? Scratch? I can't find it).
But [0] is never possible.
whats the difference between const and mutable?
You don't need both a "const" keyword and a "mutable" keyword in a programming language. You only need 1 of the keywords, because the other can be the default. munchler is saying the "const" keyword shouldn't exist, and instead all variables should be constant by default, and we should have a "mutable" keyword to mark variables as mutable.
As opposed to how C++ works today, where there is no "mutable" keyword, variables are mutable by default, and we use a "const" keyword to mark them constant.
What if the lang has pointers? How express read-only?
const ptr; // can't reassign, can't write-through (if r/o by default)
const mut ptr; // can writeF# is wonderful. It is the best general purpose language, in my opinion. I looked for F# jobs for years and never landed one and gave up. I write Rust now which has opened up the embedded world, and it's nice that Rust paid attention to F# and OCaml.
Python has a lot of functional-like patterns and constructs, but it's not a pure functional language. Similarly, Python these days allow you to adds as much type information as you want which can provide you a ton of static checks, but it's not forced you like other typed languages. If some random private function is too messy to annotate and not worth it, you can just skip it.
I like the flexibility, since it leads to velocity and also just straight up more enjoyable.
Trying to do it 100% pure makes everything take significantly longer to build and also makes performance difficult when you need it. It’s also hard to hire/onboard people who can grok it.
his comment was in response to the
```
Rediscovering one of the many great decisions that Erlang made
```and seemed to me to insinuate just that.
I want to be able to write `x := y` and be sure I don't have mutable slices and pointer types being copied unsafely.
I find many of languages I am constantly fighting with dependency managers, upgrades and all sorts of other things.
I don't mind the idea here, seems good. But I also don't move a block of code often and discover variable assignment related issues.
Is the bad outcome more often seen in C/C++ or specific use cases?
Granted my coding style doesn't tend to involve a lot of variables being reassigned or used across vast swaths of code either so maybe I'm just doing this thing and so that's why I don't run into it.
It is not about mutable/immutable objects , it is about using a name for a single purpose within given scope.
a = 1
b = 2
a = b
"a" name refers to the "2" object. "1" hasn't changed (ints are immutable in Python). If nothing else references it, it might as well disappear (or not).Though both "single purpose" and immutability may be [distinct] good ideas.
1) You get intermediate results visible in the debugger / accessible for logs, which I think is a benefit in any language.
2) You get an increased safety in case you move around some code. I do think that it applies to any language, maybe slightly more so in C due to its procedural nature.
See, the following pattern is rather common in C (not so much in C++):
- You allocate a structure S
- If S is used as input, you prefill it with some values. If it's used as output, you can keep it uninitialized or zfill.
- You call function f providing a pointer to that struct.
Lots of C APIs work that way: sockets addresses, time structures, filesystem entries, or even just stack allocated fixed size strings are common.
Every new state is an additional condition to check. A mutated variable implies an additional state to test.
One boolean means two states to test. Four booleans that interact is 2^4 (16) states to test.