| ▲ | IshKebab 13 hours ago |
| Yeah this 100%. FP isn't hard because of FP - it's all the other things that popular FP languages come with that makes them hard to understand. Arguably most of them are not related to FP; they're just style choices: 1. Global type inference. 2. Implicit syntax (no brackets for function calls, commas to separate arguments, semicolons to end statements/expressions, etc.) 3. Currying & point free style. 4. Tendency to have very deep nested expressions. The gap between `let foo =` and it's actual value can often be hundreds of lines. I'm sure you can write FP code that avoids these issues and is easy to follow but it doesn't seem like people do that in practice. Rust avoided all of these issues fortunately. (Oh I forgot about monads.) |
|
| ▲ | amluto 12 hours ago | parent | next [-] |
| Also: avoidance of mutable state, even locally. A lot of functions can be more straightforwardly expressed using mutable variables and data structures, and most functional languages can handle local mutable state, but a lot of code in functional languages avoids it. Conversely, a lot of code written in imperative languages would be clearer and/or less bug-prone if it avoided mutable state and used persistent data structures. I wish there was a mainstream, high performance language that made both styles equally ergonomic. |
| |
| ▲ | calebh 11 hours ago | parent | next [-] | | In my experience the main benefit of functional programming is function purity. I am completely fine with mutation inside of a function since the all of the mutation logic is self-contained in a single small block of text. I think everyone should take a shot at writing a non-trivial functional program to see the benefit. Once you understand what makes it great, you can apply what you've learned to the majority of OOP/impure languages. | |
| ▲ | brabel 10 hours ago | parent | prev | next [-] | | Flix has "regions", which are a way to allow mutation locally while keeping the language purely functional otherwise: See Region-based Local Mutation in https://flix.dev/ | |
| ▲ | Philpax 12 hours ago | parent | prev | next [-] | | Does Rust not meet that description? | | |
| ▲ | amluto 12 hours ago | parent [-] | | Rust is very good at making things immutable. But that doesn't mean it's particularly fun to, for example, append or prepend something to a list and retain a reference to both the old and the new list. Compare to most "functional" languages, in which prepending an item to a list and ending up with immutable references to the old and new lists is almost the defining feature of the language. | | |
| ▲ | Mond_ 11 hours ago | parent | next [-] | | Considering the performance implications of a full commitment to immutable data structures, I think Rust does a pretty good job here. Dynamic arrays / vectors / slices or whatever you want to call them are probably the most important or fundamental non-plain-old-data data structure, and in a purely immutable environment these are essentially impossible (or have awful performance characteristics). | |
| ▲ | IshKebab 11 hours ago | parent | prev [-] | | Yeah although I find FP languages' obsession with singly linked lists to be a real flaw because they have such awful performance characteristics. And also pretty bad ergonomics often. The number of times stuff comes out backwards... |
|
| |
| ▲ | 12 hours ago | parent | prev | next [-] | | [deleted] | |
| ▲ | delta_p_delta_x 12 hours ago | parent | prev [-] | | > I wish there was a mainstream, high performance language that made both styles equally ergonomic. Unironically, C++. | | |
| ▲ | Mond_ 11 hours ago | parent | next [-] | | > > I wish there was a mainstream, high performance language that made both styles equally ergonomic. > Unironically, C++. At best C++ falls under "equally unergonomic". | |
| ▲ | ndriscoll 12 hours ago | parent | prev [-] | | Or depending on what "high performance" means (e.g. if Java or Go would be considered acceptable), Scala. |
|
|
|
| ▲ | elevation 12 hours ago | parent | prev | next [-] |
| > I forgot about monads I've been solving business problems with code for decades. I love pure, composable functions, they make my job easier. So do list comprehensions, and sometimes, map and filter. Currying makes sense. But for the life of me, no forum post or FP tutorial that I could find explained monads in clear language. I've googled "what is a monad" once a year, only to get the vague idea that you need monads to handle IO. I wondered if my brain was broken, but now I'm wondering if most FP adherents are simply ineffective communicators: they've got an idea in their head but can't/won't express it in a way that others after them can understand. In other words, the exact same reason why TFAuthor was corrected by his employer. |
| |
| ▲ | ekidd 11 hours ago | parent | next [-] | | So, one way to understand a monad is that it's essentially a container type with "map" and "flatten" operations. Let's say your monad is type M<T>. The "map" operation allows you to take a function T->U and a container M<T>, and transform each element, giving you a container M<U>. "Flatten" takes a container of type M<M<T>> and returns a container of type M<T>. So a List<List<Int>> becomes a List<Int>. Now comes the trick: combine "map" and "flatten" to get "flatMap". So if you have a M<T> and a function T->M<U>, you use "map" to get an M<M<U>> and "flatten" to get an M<U>. So why is this useful? Well, it lets you run computations which return all their values wrapped in weird "container" types. For example, if "M" is "Promise", then you can take a Promise<T> and an async function T->Promise<U>, and use flatMap to get a Promise<U>. M could also be "Result", which gets you Rust-style error handling, or "Optional", which allows you to represent computations that might fail at each step (like in languages that support things like "value?.a?.b?.c"), or a list (which gets you a language where each function returns many different possible results, so basically Python list comprehensions), or a whole bunch of other things. So: Monads are basically any kind of weird container that supports "flatMap", and they allow you to support a whole family of things that look like "Promise<T>" and async functions, all using the same framework. Should you need to know this in most production code? Probably not! But if you're trying to implement something fancy that "works a bit like promises, or a bit like Python comprehensions, or maybe a bit like Rust error handling", then "weird containers with flatMap" is a very powerful starting point. (Actual monads technically need a bit more than just flatMap, including the ability to turn a basic T into a Promise<T>, and a bunch of consistency rules.) | | | |
| ▲ | AdieuToLogic an hour ago | parent | prev | next [-] | | > But for the life of me, no forum post or FP tutorial that I could find explained monads in clear language. I've googled "what is a monad" once a year, only to get the vague idea that you need monads to handle IO. Another phrase to search for is "railway oriented programming"[0], which technically describes a monad called a Kleisli[1]. Still, there are many introductions in a lot of languages based on the term above. HTH EDIT: Something to keep in mind is that the term "monad" identifies a mathematical concept having well-defined behaviors, not a specific construct. So searching for "what is a monad" is akin to searching for "what is a happiness." 0 - https://medium.com/geekculture/better-architecture-with-rail... 1 - https://bartoszmilewski.com/2014/12/23/kleisli-categories/ | |
| ▲ | shakadak 6 hours ago | parent | prev | next [-] | | You've mentioned list comprehensions, map, and filter, so I suppose you mostly used these concepts with lists/arrays. One question you could ask yourself is, how could you reproduce list comprehensions without special syntax ? Another way to view monads is by following the types. With map, you can chain pure functions from one type to another to be applied on lists (hence the (in -> out) -> ([in] -> [out]) ) . How would you do that chaining with function from one type to another but wrapped in a list ( (in -> [out]) -> ([in] -> [out]) ) ? Then you can think about how it could be applied to other types than lists, for example, nullable/option types, result types, async/promise types, and more hairy types that implement state, reading from an environment, etc... | |
| ▲ | Asraelite 11 hours ago | parent | prev | next [-] | | https://tech.nextroll.com/blog/dev/2022/11/11/exploring-mona... The thing that always makes FP concepts click for me is seeing them explained in a language that isn't Haskell or similar. I don't know why people are so obsessed with using Haskell for tutorials. Its syntax is a nightmare for newcomers and you can do FP in so many other languages. | | |
| ▲ | mrkeen 10 hours ago | parent [-] | | For instance, C#: public static System.Collections.Generic.IEnumerable<TResult> SelectMany<TSource,TCollection,TResult>(this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,System.Collections.Generic.IEnumerable<TCollection>> collectionSelector, Func<TSource,TCollection,TResult> resultSelector);
compared to: (>>=) :: m a -> (a -> m b) -> m b
Guess which one I googled, and which one I typed from memory. | | |
| ▲ | mrsmrtss 9 hours ago | parent | next [-] | | In your c# sample full namespaces are unnecessary and will add only noise in this context. | |
| ▲ | 9 hours ago | parent | prev [-] | | [deleted] |
|
| |
| ▲ | 12 hours ago | parent | prev | next [-] | | [deleted] | |
| ▲ | OhMeadhbh 8 hours ago | parent | prev | next [-] | | https://www.bi6.us/CO/N/20250330.HTML#/033002 | |
| ▲ | vjerancrnjak 12 hours ago | parent | prev | next [-] | | I felt similar with lenses. The problem lens solve is horrible. You don’t even want that problem. FP can be the pragmatic as well. You’re going to glue up monad transformers, use lenses like there’s no runtime cost, and compute whatever you need in days but at least you know it works. Maybe there’s accidentally quadratic behavior in lifting or lenses but that’s by design. The goal is to just throw software at things as fast as possible as correctly as possible. | | |
| ▲ | AdieuToLogic an hour ago | parent [-] | | > I felt similar with lenses. The problem lens solve is horrible. You don’t even want that problem. Lenses abstract properties in a composable manner. How is this problem horrible? > FP can be the pragmatic as well. You’re going to glue up monad transformers, use lenses like there’s no runtime cost, and compute whatever you need in days but at least you know it works. Maybe there’s accidentally quadratic behavior in lifting or lenses but that’s by design. The goal is to just throw software at things as fast as possible as correctly as possible. Any abstraction can be used inappropriately. Slavish adherence to an approach in spite of empirical evidence is a statement about those making decisions, not the approach itself. In other words: A poor craftsman blames his tools.
|
| |
| ▲ | dionian 11 hours ago | parent | prev | next [-] | | As a Scala/FP newbie, i was stuck in this rut for years then i just stopped caring. it actually doesnt matter to me much. ZIO helped due to the eschewing of mathematical terms (compared to things like Cats ). In other words, you don't really need to know to get things done. Monads are incredibly intuitive once you start composing them. | |
| ▲ | eddlgtm 12 hours ago | parent | prev [-] | | Monads are just monoids in the category of endofunctors. /s Monads are, in my head, just a wrapper around a type. Or a box another type is inside. For example we can have an Int and we can put the Int in a box like Maybe<Int>. Imagine Python code that gets a value from a function that is an Int or None, then another function that takes an Int and returns an Int or None, then another function that takes an Int and returns an Int or None. How hellish is that to handle? If not None, if not none, if not none, ad nauseam... In Haskell I could use a Traverse function that takes a monad and passes the Int value to the next function or handles the None error, avoiding all the boilerplate. Other Monads are like the State monad - a box that contains some variables I want to maintain over functions. Or an IO monad to handle network calls / file calls. It's probably not a perfect analogy, but I work with functional languages and it tends to hold up for my beginner/intermediate level. |
|
|
| ▲ | mrkeen 10 hours ago | parent | prev | next [-] |
| > (Oh I forgot about monads.) This demands an equivalent article which takes the same form as TFA. A coworker complains about monads, so the manager insists all monads be taken out. So Brian gets to work on the codebase, removing all Lists, Optionals, Streams, Functions, Parsers, Transactions, ... |
|
| ▲ | jen20 12 hours ago | parent | prev [-] |
| Also the unwillingness of so many enterprise developers to learn anything that wasn't commonplace in 2001, regardless of whether they were in the workforce by then. |