My team has just spent 20 minutes trying to work out if this library is satire.
I love functional programming (use Elixir at work a fair amount), but almost every one of these examples is harder to read than than the equivalent "classic" Python (https://returns.readthedocs.io/en/latest/).
Plus, if you're going to write Python then deviating majorly from the normal approaches is going to require maintainers to learn a whole new way of doing things. Given the tools that Python provides, I don't think that's worth it for the benefit. Perhaps for some specific applications?
There's libraries like these for many languages and no, they are not satire. I see it as a misguided attempt to bring some patterns from languages that are "better" (by some metrics) into a language that doesn't have good support for them.
By my experience, younger programmers are more likely to want to do this. I fell into this trap myself with a similar library for TypeScript, and discovered the hard way that code written this way was much less readable and debuggable.
Less readable because you're essentially reimplementing variable assignment and control flow with lambdas and bespoke syntax that looks out of place.
Less debuggable because the stack traces from these things blow up exponentially, and make it very hard to understand where an error came from.
So yeah, it's not satire, just misguided. If you have to use Python you're much better off sticking to the usual patterns and apply more static analysis on top rather than trying to reinvent the language this way.
I am by no means the best to judge functional programming implementations but I find the examples provided as pretty confusing. The parts where the library is supposed to make things better actually look worse to me. The original Python examples also looks like cherry picked code that was already not written very well.
if user is not None:
balance = user.get_balance()
if balance is not None:
credit = balance.credit_amount()
if credit is not None and credit > 0:
discount_program = choose_discount(credit)
I would probably write the python closer to this for readability. Don't judge me too harshly I just rewrote this quickly.
if user is None:
return
balance = user.get_balance()
if balance is None:
return
credit = balance.credit_amount()
if credit is not None and credit > 0:
discount_program = choose_discount(credit)
IMO, the problem with the original code is not in the way it nests ifs, it's in the bad design. If it is properly nested in a function, user should not ever be None (any user-less behaviour should be in a completely separate execution branch, likely in a different module), while there is no reason for get_balance and credit_amount to ever return None.
without a context that might make it reasonable, the original code is fairly absurd: where is the error handling?
The bad cases of several if clauses should be by far the largest and least obvious portion of the code, not silently omitted and further obscured by hiding branches.
I'd also add a "return None" at the end to be explicit. Implicit behaviors (like Python's implicit return None in functions) creates a tendency for human mistakes.
My dude. I have yet to see an FP article that is not just badly cherry picked anecdotes, where the cherry picking itself is often somewhere between a misrepresentation and a blatant lie (nobody would ever do <that> but you can if you need to)
It’s one of the reasons I strong advocate against functional programming. The community representation of it is, at best, highly misrepresentative of reality.
Regardless of how you feel it gets represented by various communities, it is a powerful form of programming.
I'm not a functional expert, I have only dabbled. I once rewrote a fairly complex imperative Python text processing program in a functional style. It became much clearer. Most of the code was actually reusable higher order functions, and the program logic became very short and essentially declarative.
No, you should not. Checking for None is relevant when you care about the case where it's important. But that doesn't mean every case is one. In the example here, all we care is if we can do something, and everything else doesn't matter. Which is also the reason why I could shortened credit from two checks to one.
Walrus is great but your example of nesting 3 deep looks just as terrible as the original example.
As for being obsessed with None. I usually lean towards it when no type has been provided like in the example. I think its appropriate when we are expecting it to be a None just to miss any traps further down the road. Just like typing in Python, you don't have to do it but I find it eliminates issues later on and makes it easier for others to read.
Beyond that I found that .bind_optional(...) chain full of lambdas that was supposedly superior to be far less readable than the original. But like, you my main complaint about the nesting not being my style and not that "is not None" is offensive. Particularly if you understand python... despite the links in that section, None is not a null pointer.
Otherwise railroading (which this sort of thing enables) is pretty powerful. It's just about always ugly, but it's nice at times.
This seems like too much effort has been put into it for it to be a purely satirical exercise, but I really cannot see why anyone would prefer to use this within Python, rather than (say) reaching for a proper type-safe functional language and either using that directly, or calling out to it from Python code.
As a person who teaches functional programming at degree level, this is the kind of thing that would put people off FP before they even get in the door. It is obviously less ergonomic than standard Python, and the code you end up with is no safer or more abstracted than what you started with.
That said, if the authors really do think it's a better way for programming in Python, and it works for them, then more power to 'em.
i have fastapi server which gets and mangles data. and calls numpy with that. data is badly formatted, i need to make it right. functional code is better for that. i do not want one more service now to be added in typescript, with effect ts.
there are no apis for haskell or other functional langs for data i need.
I completely agree with you. As a Haskeller, I find it immensely irritating when people try to force ‘FP’ into programming languages where it doesn’t fit. It just ends up putting people off functional programming, which is a very nice paradigm in languages which properly support it. I feel that the best code is that which adheres to the general paradigm and style of the language it is written in, and these kinds of libraries go against that.
What’s worse, these versions of ‘FP’ often bear very little resemblance to actual functional programming and its advantages. Rather, people seem to fall into the trap of confusing functional programming with ‘those fancy words I hear from Haskell’. Now, I may like Haskell, but its concepts are only useful because of the rest of the language — you can’t just port ‘monads’ and ‘IO’ into some random language and expect them to be usable. It looks like this library partly avoids that fate, in that it does have more than just ‘monads’… but yes, the monads are still there, and they’re just as clunky as you’d expect from Python.
I mean, you can... But only with great discipline and experience. It just (probably) won't work if you're having to mentor more junior engineers with an OOP background.
Do you think that style of error handling works in Scala? It certainly seems possible to get right but the quality of Scala code I've worked with in different roles has frequently been atrocious, I'd almost rather dig into some old ColdFusion or PHP.
(Funny, I was downright terrified 15 years ago about ever mentioning "ColdFusion" or "Sharepoint" in a forum because I'd get contacted by recruiters about the first and salespeople about the second. I'd always tell the salespeople that we had no budget at all for Sharepoint and just had one for our dev team because you could get the license for free with an MSDN subscription)
To be perfectly honest, I don't know Scala very well.
I'm more interested in "pure FP" languages than in multi-paradigm languages, because the former seem much more coherent in design to me. IMO, functional programming isn't about adding extra degrees of freedom (or "extra features"), it's about working within very well-defined and rigid constraints (what one might call safeguards).
As a bit of an observation, and I suppose judgement, Python developers will seemingly do everything possible to not use another language. It is a strange phenomenon, but it partially explains why there are so many libraries There is a lot of reinventing the wheel, or trying to at least.
None is the third-worst mistake in computer science, Maybe is the second-worst, the Result that can contain either a result or an exception is the worst.
I have debugged so much code that does the wrong thing with a Result (or other error handling). I will always remember the engineering manager who boasted about how we (1) did careful error handling with monads and (2) did code reviews. Oddly though I never got my code reviewed and I found so much code that silently dropped errors out of carelessness to believe there had never been code reviews. In my experience given a malfunctioning Scala class you can: (3) screw around with it for a few days or (4) write it in Java and have it working in an hour.
(A counter to this is that sometimes you do want to send a result-or-exception to some other part of the program that involves storing it somewhere and Result is really great for that… but please learn to get over any fear of exceptions you have and ignore the people who want to stigmatize them.)
Whew! <- The sigh of relief after reading comments here and realizing that I'm not the only one preferring the standard style of writing Python instead of this "functional"(??) style...
I get that Optional return values can force the programmer to handle errors without resorting to exceptions, but I can't help but wonder whether the first example with all those `bind_optional` ... is just Objective-C's nil messaging in fancy disguise...
FWIW in Objective C this is exactly what one writes (even if those functions can return nil):
credit = user.get_balance().credit_amount()
if credit > 0:
discount_program = choose_discount(credit)
I'm not sure what's so "functional" about it though.
Looks like this library is serious. Most of what I see can be written more succinctly using existing Python functionality:
1. Maybe -> Optional[T]
2. Result -> Union[SuccessT, FailureT]
3. Future, FutureResult -> concurrent.futures.Future[T]
4. IO -> don't use this; none of the standard library I/O supports it
I do appreciate the use of "container" as a term instead of "monad".
I also appreciate the typechecker enhancements being put forward by their custom plugins.
They're both perfectly valid; but the expression style [0] is shorter and more obvious, especially compared to Optional. Even the documentation for Optional states [1]:
Note that this is not the same concept as an optional argument, which is one that has a default.
The main source of complexity here comes from the design of the python language. The ideas this library is based on actually come from languages that seek absolute mathematical simplicity.
What if it is not actually more complicated, but just contains concepts and patterns you haven't learned yet or are not used to? All this library offers is a way of composing functions, the kinda stuff that comes default in many languages.
I don't really disagree with other commenters saying that there are easier ways to read and write code in Python...
However:
- I think the response is excessively negative, there are some really great patterns in here
- I put most of the blame on the ugly "design" of the python language (and a lack of guardrails which takes away a lot of the value a library like this would otherwise provide)
- I can easily imagine this library, or an interface similar to it, being used on a large code base in a production/commercial setting. I think people who can't imagine that probably just don't have a lot of imagination.
People in this thread seem to be conflating "it's less readable" with "it's written in a dialect I'm not used to". The samples are perfectly readable to anyone who spent some time with a languages that offers similar functionality, other criticisms notwithstanding.
> People in this thread seem to be conflating "it's less readable" with "it's written in a dialect I'm not used to".
I have long been arguing that this is exactly what "readability" means. Readability is completely subjective, and depends on personal experience with various languages, types of syntax and so on.
I use Haskell as my main language. I’m very familiar with its concepts. I’ve also used Python, so I’m familiar with that too. And this Python code strikes me as singularly unreadable, both compared to its ‘non-functional’ equivalent, and to the corresponding Haskell.
Yes, but now you are requiring people to know both Python AND Haskell/StandardML/Whatever in order to maintain your Python app.
Python is not just a "base" language, it's also a set of patterns for how you go about writing code in it. Conforming to those patterns to some degree makes for (subjectively) nice code, and (objectively) easier to understand code because of the familiarity.
Ah great, I've been trying to make my Python look more like Scala!
In all honesty, Return's docs call out how problematic if-else chaining is, and then proceeds to replace it with chained lambdas. How is that better?
I'd go as far as to say that we can avoid a lot of the null coercion stuff with proper encapsulation, but that is basically saying, "We can solve Python's FP challenges with better object design," and I understand how that may sound like nails on a chalkboard to some.
If this helps you write the kind of code you and your peers want and can understand, who am I to judge?
57 comments
[ 1.8 ms ] story [ 123 ms ] threadI love functional programming (use Elixir at work a fair amount), but almost every one of these examples is harder to read than than the equivalent "classic" Python (https://returns.readthedocs.io/en/latest/).
Plus, if you're going to write Python then deviating majorly from the normal approaches is going to require maintainers to learn a whole new way of doing things. Given the tools that Python provides, I don't think that's worth it for the benefit. Perhaps for some specific applications?
By my experience, younger programmers are more likely to want to do this. I fell into this trap myself with a similar library for TypeScript, and discovered the hard way that code written this way was much less readable and debuggable.
Less readable because you're essentially reimplementing variable assignment and control flow with lambdas and bespoke syntax that looks out of place. Less debuggable because the stack traces from these things blow up exponentially, and make it very hard to understand where an error came from.
So yeah, it's not satire, just misguided. If you have to use Python you're much better off sticking to the usual patterns and apply more static analysis on top rather than trying to reinvent the language this way.
Maybe with an additional .get() at the end to unwrap it? Though I imagine a dynamically-created class could avoid that.
there's a PEP. But it's been pocket-vetoed, i think.
My dude. I have yet to see an FP article that is not just badly cherry picked anecdotes, where the cherry picking itself is often somewhere between a misrepresentation and a blatant lie (nobody would ever do <that> but you can if you need to)
It’s one of the reasons I strong advocate against functional programming. The community representation of it is, at best, highly misrepresentative of reality.
I'm not a functional expert, I have only dabbled. I once rewrote a fairly complex imperative Python text processing program in a functional style. It became much clearer. Most of the code was actually reusable higher order functions, and the program logic became very short and essentially declarative.
Why are people so obsessed with None? The explicit check is usually unnecessary. Just check directly.
[1] https://www.inspiredpython.com/article/truthy-and-falsy-gotc...
As for being obsessed with None. I usually lean towards it when no type has been provided like in the example. I think its appropriate when we are expecting it to be a None just to miss any traps further down the road. Just like typing in Python, you don't have to do it but I find it eliminates issues later on and makes it easier for others to read.
Otherwise railroading (which this sort of thing enables) is pretty powerful. It's just about always ugly, but it's nice at times.
optional.py seems to address the same issue and with a slightly better approach (but still ugly).
https://github.com/Python-Optional/optional.py
As a person who teaches functional programming at degree level, this is the kind of thing that would put people off FP before they even get in the door. It is obviously less ergonomic than standard Python, and the code you end up with is no safer or more abstracted than what you started with.
That said, if the authors really do think it's a better way for programming in Python, and it works for them, then more power to 'em.
there are no apis for haskell or other functional langs for data i need.
What’s worse, these versions of ‘FP’ often bear very little resemblance to actual functional programming and its advantages. Rather, people seem to fall into the trap of confusing functional programming with ‘those fancy words I hear from Haskell’. Now, I may like Haskell, but its concepts are only useful because of the rest of the language — you can’t just port ‘monads’ and ‘IO’ into some random language and expect them to be usable. It looks like this library partly avoids that fate, in that it does have more than just ‘monads’… but yes, the monads are still there, and they’re just as clunky as you’d expect from Python.
</rant>
I mean, you can... But only with great discipline and experience. It just (probably) won't work if you're having to mentor more junior engineers with an OOP background.
(Funny, I was downright terrified 15 years ago about ever mentioning "ColdFusion" or "Sharepoint" in a forum because I'd get contacted by recruiters about the first and salespeople about the second. I'd always tell the salespeople that we had no budget at all for Sharepoint and just had one for our dev team because you could get the license for free with an MSDN subscription)
I'm more interested in "pure FP" languages than in multi-paradigm languages, because the former seem much more coherent in design to me. IMO, functional programming isn't about adding extra degrees of freedom (or "extra features"), it's about working within very well-defined and rigid constraints (what one might call safeguards).
I have debugged so much code that does the wrong thing with a Result (or other error handling). I will always remember the engineering manager who boasted about how we (1) did careful error handling with monads and (2) did code reviews. Oddly though I never got my code reviewed and I found so much code that silently dropped errors out of carelessness to believe there had never been code reviews. In my experience given a malfunctioning Scala class you can: (3) screw around with it for a few days or (4) write it in Java and have it working in an hour.
(A counter to this is that sometimes you do want to send a result-or-exception to some other part of the program that involves storing it somewhere and Result is really great for that… but please learn to get over any fear of exceptions you have and ignore the people who want to stigmatize them.)
I get that Optional return values can force the programmer to handle errors without resorting to exceptions, but I can't help but wonder whether the first example with all those `bind_optional` ... is just Objective-C's nil messaging in fancy disguise...
FWIW in Objective C this is exactly what one writes (even if those functions can return nil):
I'm not sure what's so "functional" about it though.This is a library about composing function, can't get more functional than that.
I also appreciate the typechecker enhancements being put forward by their custom plugins.
[0]: https://docs.python.org/3/library/stdtypes.html#types-union
[1] https://docs.python.org/3.11/library/typing.html#typing.Opti...
Don't let unfamiliarity get in the way!
However:
- I think the response is excessively negative, there are some really great patterns in here
- I put most of the blame on the ugly "design" of the python language (and a lack of guardrails which takes away a lot of the value a library like this would otherwise provide)
- I can easily imagine this library, or an interface similar to it, being used on a large code base in a production/commercial setting. I think people who can't imagine that probably just don't have a lot of imagination.
Simplicity =/= familiarity
I have long been arguing that this is exactly what "readability" means. Readability is completely subjective, and depends on personal experience with various languages, types of syntax and so on.
Code should be judged on its own merit, and in that case your definition of "readability" doesn't really help us go anywhere.
Python is not just a "base" language, it's also a set of patterns for how you go about writing code in it. Conforming to those patterns to some degree makes for (subjectively) nice code, and (objectively) easier to understand code because of the familiarity.
In all honesty, Return's docs call out how problematic if-else chaining is, and then proceeds to replace it with chained lambdas. How is that better?
I'd go as far as to say that we can avoid a lot of the null coercion stuff with proper encapsulation, but that is basically saying, "We can solve Python's FP challenges with better object design," and I understand how that may sound like nails on a chalkboard to some.
If this helps you write the kind of code you and your peers want and can understand, who am I to judge?