I had a period of a couple of years in uni when I was very into Haskell and using it whenever it made sense to me (which was a lot).
I haven't touched it many years (save for the occasional amending of my xmonad conf) and would need a lot of time to get back to reading it properly, let alone code in it.
Still, I'm very happy I did that and look back at it fondly. It got me grokking the functional mindset and paradigm that still influence the design choices I make and the code I write heavily.
Today, I'd push for learning Idris instead, or maybe using Haskell as a brief intro to Idris. While young, it's a lot more approachable and makes working with statefulness and IO a lot more intuitive, without sacrificing purity.
When a variable can be reassigned it adds the complexity of time dependence. Meaning that if you don't keep track of the order of values that variable was assigned then you probably can't understand the program. The problem is compounded by the size of the scope that variable exists in. That's why we avoid using global variables, globals force us to track the order of updates from potentially anywhere in the code. The problem is further compounded if there are multiple globals that depends on each other. Then you need to track the relative order of updates across all globals. That's the worst case scenario.
In OOP you try to limit the scope of state, private variables inside an object can update its value but can only be directly accessed from inside the object. But still those objects are inherently stateful. In order to understand an object, how you can use it, whether it is working correctly, you are still forced to understand where/when it is in the timeline of updates.
Purity means that if you understand a variable or object in one place in your code then you understand it from anywhere in your code. You don't need to know how it was used before because it can't be changed. If it were changed then you would know because it would now be a different variable or object.
I would further add that in day-to-day code, I don't necessarily write "pure" functions. I use a strategy based on my experiences with Haskell, but spiced with a bit more pragmatism. For any significant bit of code past a couple of screens or so, I almost always have a clean separation between the "business logic" code, and the "IO" code, such that I can plug different "IO" codes into the business logic code trivially.
The business logic code is not technically "pure", in that it calls out to IO code freely. But it means I can swap out the IO code for testing, and drive the business logic with any input I desire in the process. It also lets me test the IO code directly if that is useful/necessary/desirable, without the business logic getting in the way, which it often does!
Theoretically, you could transform this to "pure" code, by gathering everything into one big data structure in the IO code, then feeding it to the business logic, but this often comes with a lot of inconvenience and even performance issues (like gathering expensive things you might need but not using them).
One of the things that Haskell can "put into your fingers" is a sense of what code does IO. Even if you nominally know, you probably don't instinctively know if you've never used a language that rigidly forced you to be correct. It is a common experience for quite a while in Haskell to write a pure function, that calls a pure function, that calls a pure function... that, err... needs to read just a little bit from a file according to the way you've structured things. Whoops. And you learn to restructure things to move the reading back "up" in the code to constrain the purity, even though it's just a "little bit", and over time you get better at not making the mistake in the first place. If you are not trained by Haskell, it is really easy to end up thinking "oh, it's just a little bit of impurity... it'll be fine"... and maybe, in fact, it will. But this still starts to add up. I still write "impure" code... but I do so much more carefully now.
The number of times students have come to me absolutely baffled as to why they can't tokenize a string twice using strtok() is the best reason for function purity I can think of. It's such an abomination of a function. The first time you call it, you give it a string and a delimiter. Subsequent times you call it, you give it a NULL and the delimiter. And then once you've tokenized the string once, you can never use it again. It's an example I hold up of how not to design functions.
> An expression is called referentially transparent if it can be replaced with its corresponding value (and vice-versa) without changing the program's behavior. This requires that the expression be pure, that is to say the expression value must be the same for the same inputs and its evaluation must have no side effects.
> In mathematics all function applications are referentially transparent, by the definition of what constitutes a mathematical function. However, this is not always the case in programming, where the terms procedure and method are used to avoid misleading connotations. In functional programming only referentially transparent functions are considered.
> The importance of referential transparency is that it allows the programmer and the compiler to reason about program behavior as a rewrite system. This can help in proving correctness, simplifying an algorithm, assisting in modifying code without breaking it, or optimizing code by means of memoization, common subexpression elimination, lazy evaluation, or parallelization.
> The concept seems to have originated in Alfred North Whitehead and Bertrand Russell's Principia Mathematica (1910–13). It was adopted in analytical philosophy by Willard Van Orman Quine.
Not op but they may mean the term-of-art where a pure function has no internal state and no side effects, so it always has the same outputs given the same inputs. Although it's not technically part of the definition of functional purity, I'd add that's it's nice to have a function whose behavior is defined for every input in the domain.
I haven't touched it many years (save for the occasional amending of my xmonad conf) and would need a lot of time to get back to reading it properly, let alone code in it.
Still, I'm very happy I did that and look back at it fondly. It got me grokking the functional mindset and paradigm that still influence the design choices I make and the code I write heavily.
Today, I'd push for learning Idris instead, or maybe using Haskell as a brief intro to Idris. While young, it's a lot more approachable and makes working with statefulness and IO a lot more intuitive, without sacrificing purity.