10 ms·
I mean, in an example where the problem and solution is well understood, sure, but like all things there are downsides and you need to consider appropriateness.
by Vanit 4y ago
I mean, in an example where the problem and solution is well understood, sure, but like all things there are downsides and you need to consider appropriateness.
Firstly if you're conceiving a novel algorithm it can be tedious to debug/test if written in a functional style because it's hard to step through in the debugger.
Second, you need to be extremely familiar with the language and ensure that you know that you're not mixing and matching mutable and immutable system calls, which will be hell to debug, see 1.
I'm not anti-functional and sometimes it can yield really clean and easy to read code, but I have to recoil when I see praises sung without the criticism.
- weatherlight 4y agoYour first point, I'm not sure that is true. If you are using a functional style in a language that supports side effects, its still pretty easy to debug. you could implement a tap function that console logs the value, then then returns the result of the previous function in the composition/chain. If you are using a lazy evaluated language like Haskell, instead of the stack trace you're used to in an imperative programing language, you'd get the reductions that led up to the reduction of the expression with the breakpoint on it. The second point, However, I do agree with, especially in languages where mutability is the default.
- steego 4y agoI’m a functional fan and I don’t disagree with your points. Like anything, real life functional programming is not a panacea and most languages that append functional constructs as an afterthought often come with a price (debugging, performance) and just as you pointed out, mixing functional code with imperative code indiscriminately can yield wonderfully nuanced bugs. With that, I find languages like OCaml and F# provide a great ergonomic experience for writing both functional and imperative style code. (There’s nothing wrong with imperative code confined to the scope of a function or a well defined object) Even with great tools, I still think there’s quite a bit that can be done to improve the hidden costs incurred from a functional-first approach.
- galaxyLogic 4y ago> There’s nothing wrong with imperative code confined to the scope of a function Thhat is a fine point which I rarely see discussed in connection with FP. (Pure) FP is commonly understood (?) to mean that there can be no mutable data. You can "bind" a value to a variable but only once. Right? But within a function you can have local variables. I don't see why it would be bad to assign multiple different values to the same local variable, which only exists during the execution of the function. The variable only exists in the "stack" not in the "heap" and is gone as soon as the function returns. So if I run a while-loop and repeatedly assign a new value to the local variable "i" for instance, would that be unacceptable according to "FP"? Why? I can't see any ill effects from modifying a variable whose value can not "linger" past the execution of the function. What am I missing? Why is it (supposed to be) bad to rebind any number of different values to a local variable?
- grumpyprole 4y agoPure functional programming is really about tracking and controlling effects by making them first-class, not avoiding them altogether. This has numerous benefits for reasoning, correctness and optimisation. Haskell allows mutable local variables, 'ST' references which can be used to build functions that are observably pure on the outside. The Haskell type checker will guarantee that no mutable state escapes. This is true "state encapsulation". It is completely acceptable for pure FP programming.
- galaxyLogic 4y agoSo mutable state is not bad, as long as it is "encapsulated"? I thought (from what I've read so far) that "no mutable state" is the definition of "pure FP". So should the definition of pure FP be "No un-encapsulated mutable state"? Instead state must always be "encapsulated". Where can I read more on this? Thanks
- jeffreygoesto 4y agoSomewhere down towards the transistors, the change has to happen. The registers of the machine are explicitly designed to be mutable. I don't know how functional languages are compiled and maybe somebody who does can chime in, but I would not be surprised if the IR already was imperative. So it may be easy to give some access to that in a language. I'd say "pure" is if the calculation can be done with a function without side effects and it is a deliberate choice to restrict a language to (almost) only that. No side effects at all would mean no interaction with "the outside world" and no access to input data though.
- josephcsible 4y ago> ensure that you know that you're not mixing and matching mutable and immutable system calls Languages that were designed from the ground up to be pure functional, like Haskell, keep you from making this mistake.
- throwawaymaths 4y ago> Second, you need to be extremely familiar with the language and ensure that you know that you're not mixing and matching mutable and immutable system calls, which will be hell to debug, see 1 How is this not the case, for say, python? E.g. You can get yourself into real trouble with a function like this in python. def f(dict = {}): return dict
- lgas 4y agoIndeed. It's actually easier in languages like Haskell because the type system won't let you mix and match.
- throwawaymaths 4y agoI mean, if you then use the result of f in a mutating function, the default value will be mutated the next time you call it
- lgas 4y agoRight, but the point is that you can't actually write the equivalent of that function in Haskell. You'd have to write something like this: defaultMap :: Map k v defaultMap = ... f :: TVar (Map k v) -> IO (Map k v) f = \case Nothing -> newTVarIO Map.empty Just m -> pure defaultMap which would make it hard to be unaware of the possibility that the dictionary you get each time you pass `Nothing` would be the same one. And then the type signature returns an IO action, so you would know that "anything could happen here", whereas there's no such indication in python. Conversely, if you had your `f` function in Haskell has a type like `Maybe (Map k v) -> Map k v` then you know that it can't possibly be creating a mutable map and inserting it in the process somewhere without your knowledge. (Modulo `unsafePerformIO` and other evils like that).
- shay_ker 4y agoCan anyone elucidate why code written in a functional style is harder to debug?