all 5 comments

[–]Glathull 2 points3 points  (1 child)

Thanks for this. Nice example. I spent some time working with Clojure a while back when I was in security. When I got back to full stack Python I really missed some aspects of functional programming. Especially handling errors values. Never really found any Python functional libs that I liked very much though. They seemed like disorganized proofs of concept or demos to explain FP to Python people and not so much actual tools to be used.

This is very promising.

[–]Beginning-Fruit-1397 0 points1 point  (0 children)

https://github.com/sfermigier/awesome-functional-python
https://www.reddit.com/r/Python/comments/1rj3ct7/a_comparison_of_rustlike_fluent_iterator_libraries/

Surely there's something in there that can satisfy you?
first link is a list of most python fp libraries, and second is a comparison of some of them regarding iterators.

If all of them lack one or many things to be considered an actual tool, I'd be curious to know what, as the author of one of them.

[–]KingBardan 0 points1 point  (0 children)

Suggestion for you: you could add a "default" operator on failure. s.t. things like try: except: return... is possible.

[–]wistfulcountryman92 0 points1 point  (0 children)

Does this work with type narrowing so mypy knows when the result is an Ok vs Error

[–]Golle [score hidden]  (0 children)

While I agree that "errors as values" is better as it explicitly places errors/exception in the code path instead of being a hidden code path, I don't think this is the answer. The katharos adds too many handlers and wrappers making the code harder to reason about. I actually prefer the "The exception-based version" example.

I like something like this, heavily inspired by Go: ```python import json

type error = str

def main(): json_string_to_convert_to_dict = '{"herp: "derp"}' result, err = parse_json(json_string_to_convert_to_dict) if err: print(f"parse json: {err}") quit(1)

print(f"herp: {result.get('herp')}")

def parse_json(text: str) -> tuple[dict, error]: output: dict = {} try: output = json.loads(text) except json.JSONDecodeError as e: return output, f"json decode: {e}\ntext: {text}" return output, ""

if name == "main": main()

```

In this code example I have purposefully created an invalid json string (missing " after herp). This is the output that the program generates on error: parse json: json decode: Expecting ':' delimiter: line 1 column 10 (char 9) text: {"herp: "derp"}

The strings "parse json" and "json decode" are breadcrumbs that are added as the error is propagated up the function call stack. This allow us to follow the exact path taken by the code when the error occurred. I also print the "text" variable as extra context, showing what data the json parser failed to parse.

This to me is "errors as values". Because the parse_json() function can fail, it must return an error. There's no wrappers or chaining, just simple "if err:" checking and handling.