8 ms·
GOOD: having large chunks of code that runs (mostly) without side-effects. As projects get bigger global state can trip you up, so try to keep it in check. That
by gizmo 4y ago
GOOD: having large chunks of code that runs (mostly) without side-effects. As projects get bigger global state can trip you up, so try to keep it in check. That's where the real value of functional programming is.
BAD: copying data needlessly and turning trivial stuff into a functional pipeline that is impossible to debug. You want your code to read top to bottom like the problem you're trying to solve, if at all possible. If your call stack looks like map, fold, reduce you've introduced a ton of complexity, and why exactly?
Every programmer should understand functional programming. Higher order functions can be super useful. Immutable objects are invaluable for preventing subtle bugs. But if you feel the need to transform simple linear code into a map of higher order functions because of some abstract benefits you're probably doing stuff that's too easy for you and you should get your mental challenge from solving a more difficult problem instead.
- mejutoco 4y agoAgree, especially with having as much part of the code as pure functions. I think where functional programming really shines is in combination with a good type system, that can enforce those restrictions. In languages like js I find it not so useful (even if using map, filter, etc.) because I have no guarantees that there is a hack somewhere in the function that I did not see. When there are a lot of pure functions the cognitive load is reduced so much I can use the "extra capacity" to focus on the task.
- gizmo 4y agoIn Javascript Object.freeze() and Object.seal() are wonderful to prevent accidental mutation.
- mejutoco 4y agoI did not know that. Another one is const to declare simple variables. There are some features, I agree. But how do I declare that a function returns a specific type and have it enforced? IMO, only by switching to Typescript or similar.
- gizmo 4y agoVSCode can enforce return types with jsdoc annotations, even when using plain javascript. VSCode uses typescript internally for all type inference, so you get that for free. Type inference works flawlessly 95% of the time, even for iterators/generators/inheritance/containers.
- samsquire 4y agoI would rather inherit someone's stateful JavaScript or Java than a complicated codebase by a seasoned Clojure, Haskell, Scala developer. I stand a better chance understanding the sequential code than a complicated recursive (edit: nested) monad. Depending how it was written. I can probably understand Kafka pipeline's or a functional map, reduce pipeline. My parser for my programming language which I'm building is a recursive Pratt parser but it's written in Java. I want to understand functional programming better. I am interested in software transactional memory and parallelism. I am aware Erlang and Go use message passing but only Erlang is functional. In theory functional code can be parallelised due to immutability but you need some kind of communication to indicate transference of values. Imagine a pipeline where you need to share a value to a few other threads. I'm aware Haskell has software transactional memory
- lmarcos 4y agoAgree. The first version of the example (the one using only `map`) is way easier to understand and maintain than the rigmarole the author ended up with (writing their own functors, pipes, peakerror and what not). It's like the author didn't find the first version "complex" enough, so they had to make it more complicated for the sake of "functional programming".
- epgui 4y agoIs there even such a thing as a “recursive monad”? That sounds made up.
- WastingMyTime89 4y agoNo there is no such thing as a recursive monad. A monad is a type for which strictly defined operations are provided. It can’t be recursive by definition.
- deleted 4y ago[deleted]
- wiseowise 4y ago
- Gordonjcp 4y agoI don't get the point of "immutable objects". Why have an object at all? Why not just hardcode the values if you want them not to change?
- i_no_can_eat 4y agoimmutable means that you set them only once, typically at runtime. Not that they have the same values always, everytime you run.
- deleted 4y ago[deleted]
- dopidopHN 4y agoThose are often records from a database. There is a way to change it, it’s to make a copy of it. I like being able to trust that something is what I think it is because it cannot be something else. Meaning: if I know that something can’t change, I don’t have to check for eventual accidental change.
- Gordonjcp 4y agoBut now you've got two versions of it that aren't the same. Why is that supposed to be good?
- naasking 4y agoBecause of aliasing. Some other functions/objects/threads may have a reference to the old version. If they were in the middle of a computation using that object, they don't have to add a whole bunch of checks to ensure that the data they're holding didn't suddenly change from underneath them. This happens a lot in concurrent programming, but even in single-threaded programs it makes reasoning about the current state of the system easier.
- Gordonjcp 4y agoSo you've just made the internal state of your program inconsistent? Why is this supposed to be good?
- kalekold 4y agoI've just left a startup company who's entire backend was written in a functional style using Typescript by an agency. The only reason I can fathom why is 'why not, functional programming is cool right!'. A new dev team was created to take over and it was a disaster. It was an absolute mess of piped, mapped, reduced functions everywhere and completely unreadable and unmaintainable. I remember getting lost in a hierarchy of about 30+ piped (non db framework) functions to write a JS object to a database. I didn't stay long. Since I quit, the entire new engineering team quit and it looks like the company is going under. Functional programming is a big mistake for some real-world code.
- bigDinosaur 4y agoYou can make code in any paradigm suck. You can do horrible unmaintainable things in any language in any paradigm: Java with twenty layers of abstractions, Python with immense amounts of spaghetti, C with its hard to control safety. You can also do awful, abysmal imperative or OOP code in Typescript. So I just don't really see how you can single out FP here at all. Your codebase sucked, and whoever was hired to write it in a FP style just sucked at doing so. Sorry.
- WA 4y ago> So I just don't really see how you can single out FP here at all. Not OP but imho, it’s because FP is "sold" as the perfect solution for readability and code maintainability. Just use FP and nothing can go wrong. That’s at least the impression I get when I read about FP. The fact that one can write abysmal OOP code is nothing new.
- biorach 4y ago> Just use FP and nothing can go wrong. That’s at least the impression I get when I read about FP. To be fair that was the kind of nonsense that was being talked about OOP in the late 90's and early 2000's. There are no silver bullets, and anyone who claims otherwise is flat wrong. However most techniques have their advantages, when used well - and I'd say FP has more to offer than OOP in this context.
- brap 4y agoSome solutions are much easier to express in an imperative way. You can still write pure functions and avoid state in imperative code. Use consts, immutable objects, etc. You get some of the benefits of functional programming while still writing readable code.
- WastingMyTime89 4y ago> copying data needlessly and turning trivial stuff into a functional pipeline that is impossible to debug. You want your code to read top to bottom like the problem you're trying to solve, if at all possible. If your call stack looks like map, fold, reduce you've introduced a ton of complexity, and why exactly? Hard to follow code is mostly a consequence of point-free style in Haskell which is an heresy to be avoided at all cost. ML typically uses a piping operator to compose code instead and that leads to top to bottom code which is extremely easy to read. There is zero difference then between a map and a loop. Loops are not read top down anyway nor are function calls. It seems to me you are just more used to these indirections than other ones and are therefor blind to them. This leads to an argument which I consider a straw man personally.
- lixtra 4y agoI tried googling for an example[1] and had difficulty finding a good one. Do you have a link to a good one? [1] https://elixirschool.com/en/lessons/basics/pipe_operator https://elixirschool.com/en/lessons/basics/pipe_operator
- dpwm 4y agoHere is an example in ocaml, where |> is the pipeline operator: https://cs3110.github.io/textbook/chapters/hop/pipelining.html https://cs3110.github.io/textbook/chapters/hop/pipelining.ht...
- Chris2048 4y ago> Loops are not read top down anyway nor are function calls But their scope can be limited.
- christophilus 4y agoI'm reminded of the Kernighan quote / trope: "Everyone knows that debugging is twice as hard as writing a program in the first place. So, if you're as clever as you can be when you write it, how will you ever debug it?" This phenomenon seems to happen more frequently with FP or related tools and languages (RxJs abstraction soup comes to mind).
- piokoch 4y ago100% agree, the monstrosity that author proposes is really hard to swallow. Simple made hard. The idea to handle validation (we get HTML instead of expected JSON) by passing some Nothing object down the flow is horrible, if we expect JSON and get something unparsable, well, the best strategy is to fail fast and let know client side that we have a wrong data. Instead we show off with some fancy code structure with zero value for the user of the software.
- chriswarbo 4y ago> simple linear code If you're talking about a sequence of statements, I've rarely come across such a thing. Firstly, the mere existence of statements in a language is an incredible complication: - It forks the grammar into two distinct sub-languages (expressions and statements) - Expressions compose with each other, and with statements; but statements don't compose with expressions. - It introduces a notion of (logical) time into the semantics ('before' a statement is executed, and 'after' a statement is executed) - The times of each statement need to be coordinated. Chaining one-after-another is easy (usually with ';'), but concurrency gives an exponential explosion of possible interleavings. - The computer often cannot help us spot mistakes, and compilers have very little ability to optimise statements. This is especially silly in Python, where many common tasks require statements, which often requires rewrites (e.g. we can't put statements in lambdas; hence we often need separate function defs (which themselves are statements); pulling those out may lose access to required lexically-scoped variables, requiring even more plumbing and wrapping; urgh.) Compare this to functional code, where there's only a single language (expressions) where everything is composable; where there is no control flow (only data dependencies); there is no notion of time; mistakes are more obvious (e.g. 'no such variable' errors at compile time); compilers have lots of scope to optimise; and code is trivial to parallelise.
- chronial 4y ago> pulling those out may lose access to required lexically-scoped variables The only situation for this that I can think of is inside a list comprehension / generator expression. You are aware that you can define functions at any scope in python?
- chriswarbo 4y ago> You are aware that you can define functions at any scope in python? >>> lambda myVar: (def foo(): return myVar + 1, foo)[-1] File "<stdin>", line 1 lambda myVar: (def foo(): return myVar + 1, foo)[-1] ^ SyntaxError: invalid syntax
- 4y ago
- deleted 4y ago[deleted]
- blauditore 4y agoAvoiding side-effects is usually lumped toghether with async-style interfaces into the term "functional programming". However, the two are separate properties with separate benefits and downsides: - No side-effects makes reasoning about logic easier, code more deterministic, and tests more reliable. However, certain problems become harder to solve (e.g. simple caching or memoization). - Async-style interfaces allow for waiting on slow operations in a non-blocking fashion, meaning those don't occupy threads, thus a more economic use of resources. However, async-style code can be harder to read, write, and debug, though this varies heavily between languages and frameworks.
- wiseowise 4y agoMap, reduce and fold are much easier to reason about than imperative loops, what’s your problem exactly?
- galaxyLogic 4y agoPart of the problem with "functional pipelines" as in the article is that you are in essence creating a domain-specific language to describe the problem your program is modeling and solving. Problem is that is not a programming language supported by anybody else than you. It is not part of the standard library. If it was it would probably be of high quality, highly tested-in-practice code. But if you constructed your pipeline-framework (as in the article) by yourself you now need to understand not only the solution in terms of your created framework, but also how the framework itself exactly works. If this is a one-off problem-solution what looks like simpler code in the end hides its complexity inside its framework-code. And as you keep programming your framework is a moving target. You will improve it from time to time. Understanding it becomes a moving target too. It is a real problem for maintenance for you or anybody else who wants to use and adapt your existing code in the future. Think about having the problem and a solution and being able to describe both in English. But then you say hey if I invent this new language Klingon and express the problem and solution in Klingon, then both the problem and solution become simpler. Cool. But now you must also program the Klingon interpreter for yourself. And be aware of which version of Klingon your code is assuming is used. This is the tendency and cost of over-engineering. It is fun and natural to look for the "ideal solution", but it comes with a cost.