8 ms·
Show HN: Koda, a typesafe functional toolkit for Python
- pizza 5y agoNice. One question I have is does the compose operator preserve error messages, allow you to pinpoint which function raised an exception etc? That's the one issue I have w/ the `pipe` function from dry-python/returns; if you build a complex pipeline, the errors are somewhat inscrutable.
- keithasaurus 5y agoSo far I've been able to follow them. If you `raise` an exception during one of the functions that's used in a compose the traceback will mention the function that failed (I'm looking in 3.9 at the moment).
- hackandtrip 5y agoCan't you 'compose' together the various errors inside the `Failure` and inspect it at the end of the pipe?
- fishmarmalade 5y agoReally nice project! I’ve been looking for something like this, and typically end up building a very minimal version of it for projects. Are you looking for help or contributors? Would love to contribute if possible.
- keithasaurus 5y agoYeah, happy to have contributors. I'm not 100% certain on what the next features / changes will be, so also happy to have input, requests, questions, etc.
- impoppy 5y agoWhy would you write Python like it’s Rust? I really fundamentally do not understand the purpose. Ain’t `is Null` checks and try except good enough?
- DandyDev 5y agoBecause you'd like use the Python ecosystem, for example for writing ML code, but you'd also like to write typesafe code (or rather typechecked, using mypy)
- rmbyrro 5y agoCame here to say the same thing...
- IshKebab 5y agoYeah this sort of thing is necessary in statically typed languages like Rust and C++ but in Python (and e.g. Typescript) you can just use anonymous type unions like `str | None`. There's no need to construct a static type to hold both possibilities because variables can already hold all types.
- hackandtrip 5y agoGood luck checking for `None` equality and indent your code like 5 times... a sort of railway programming[0] avoids the awkward checks for values, making functions work with higher order types. 0: https://www.greenbird.com/news/railway-oriented-programming-in-kotlin#:~:text=Railway%20Oriented%20Programming%20(ROP)%20is,can%20be%20of%20any%20type https://www.greenbird.com/news/railway-oriented-programming-....
- ReleaseCandidat 5y agoThe original Railway: https://fsharpforfunandprofit.com/rop/ https://fsharpforfunandprofit.com/rop/ And his 'Against Railway-Oriented Programming': https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/ https://fsharpforfunandprofit.com/posts/against-railway-orie...
- impoppy 5y agoHow is this different from match case ok case err or from if result.val?
- hackandtrip 5y agoA similar "framework", very robust and used in production systems, is dry-python's one[0][1]. The approach taken is a bit different I think, since they rely heavily on `mypy` plugins to reach type safety and functional constructs otherwise impossible to get, without runtime inspections. 0: https://github.com/dry-python https://github.com/dry-python 1: https://github.com/dry-python/returns https://github.com/dry-python/returns
- keithasaurus 5y agodry-python has a lot of cool stuff. I encourage people to check it out. Koda has a slightly different approach in that it's meant to be a little simpler than returns, and to work within Python's constraints.
- exdsq 5y agoAm I strange for disagreeing with the very first example of dry-python? They suggest the second version is "much better" than the first but I sort of hate it. 1 =================== user: Optional[User] discount_program: Optional['DiscountProgram'] = None 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) 2 =================== user: Optional[User] discount_program: Maybe['DiscountProgram'] = Maybe.from_optional( user, ).bind_optional( # This won't be called if `user is None` lambda real_user: real_user.get_balance(), ).bind_optional( # This won't be called if `real_user.get_balance()` is None lambda balance: balance.credit_amount(), ).bind_optional( # And so on! lambda credit: choose_discount(credit) if credit > 0 else None, )
- properdine 5y agoI agree with you that the second is less readable and less pythonic (feels more JS/Node-ish).
- chrischen 5y agoThe second example is a pretty standard monadic composition pipeline. In languages like Haskell or F# there can be special syntax to make it easier to read (do notation or computation expressions). However if you’ve never done it will be hard to read. It’s easy to read to someone who has the background, and it is easier to refactor and less error prone because the context of a nullable value (the if statement checks) is factored out from the core logic. So if the core logic needs to be changed it can be done so without touching the if statement checking for None.
- zmmmmm 5y agoI want a similar toolkit that lifts over the Ruby/Groovy/Kotlin style functions like countBy, groupBy, sortBy, collate, etc etc. Programming in Python is such a chore without these higher level collections APIs and for some reason (I guess, list comprehensions being considered idiomatic, bare bones itertools being considered "good enough", and the lack of real inline closures?) it doesn't seem like they'll ever be part of Python proper. But I still want them.
- wswope 5y agoWith full acknowledgment that it would be better to have at least some of those in the stdlib, Pandas covers all those needs pretty well in practice, with the bonus of workarounds that sidestep CPython’s usual performance limitations.
- lauriat 5y agoThere is https://github.com/lauriat/funct https://github.com/lauriat/funct which has some of those higher level functions.
- 41b696ef1113 5y agoMaybe the toolz[0] family would cover your use cases? There is also a Cython implementation if you need better performance. [0] https://github.com/pytoolz/toolz/ https://github.com/pytoolz/toolz/
- Daishiman 5y agoThe `collections`, `itertools` and `functools` packages have most of these.
- zestyping 5y agoI was pretty puzzled by the method name "map" in the examples. Why would you call it "map" when there are no arrays or lists involved?
- RyEgswuCsn 5y agoBecause of the mathematical "map" [0] perhaps? [0]: https://en.wikipedia.org/wiki/Map_(mathematics) https://en.wikipedia.org/wiki/Map_(mathematics)
- cheriot 5y agoAre you thinking about Maybe.map in particular? It's the same name because it does the same thing: run this function on the value inside if there is one. It's a Functor :)
- dragonwriter 5y agoA Maybe is effectively isomorphic to an immutable list whose cardinality can only be zero or one.
- CodeAndCuffs 5y agox.map(f) doesnt mean "apply function f to each element in this list that we called x" x.map(f) means "apply the function f to the value within x, where x is a container of some sort" [].map(f) would do nothing, because there is nothing to pull out. ['adam', 'bill'].map(f) takes 'adam' out of the array, applies f to it, and puts it back in the array, then does the same with 'bill'. Just(5).map(f) takes 5 out of the Maybe, applies f to it, and puts it back in nothing.map(f) returns nothing. So we can map functions to values in Arrays, map functions to values in Maybes and even map functions to values in Eithers. Each time the function doesnt care that the value is in a Maybe or an Array or an Either.
- bobbylarrybobby 5y agoThe only requirement for something to be mappable is that it be iterable
- growlfrench 5y agoBrian Lonsdorf has a fun course where he explains these things: https://egghead.io/courses/professor-frisby-introduces-composable-functional-javascript https://egghead.io/courses/professor-frisby-introduces-compo...