6 ms·
Haskelling My Python
- whalesalad 2y agoGenerators are one of my favorite features of Python when used in this way. You can assemble complex transformation pipelines that don’t do any actual work and save it all for one single materialization.
- nurettin 2y agoExcited to read their next blog where they discover functools.partial and return self.
- nickpsecurity 2y agoI just discovered one from your comment. Thank you!
- BiteCode_dev 2y agoOr, you know, use numpy.
- shash 2y agoYeah, that’s much more efficient. But there’s something beautiful in the way that a Taylor expansion or a trigonometric identity emerge from the function definition. Also, it teaches an interesting concept in lazy evaluation. I mean, why not write straight up assembler? That would be even more efficient…
- brianberns 2y agoThis idea comes from a functional pearl called "Power Series, Power Serious" [0], which is well worth reading. I implemented the same thing myself in F#. [1] [0]: https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=4666d43d39c890cfe88630eb93e39afe3110f930 https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&d... [1]: https://github.com/brianberns/PowerSeries https://github.com/brianberns/PowerSeries
- abeppu 2y agoHuh, there must have been something in the water leading up to this. Also from 1998 is this paper, "Calculus in coinductive form" and neither of these cites the other. https://ieeexplore.ieee.org/document/705675 https://ieeexplore.ieee.org/document/705675
- brianberns 2y agoThese are indeed very similar. Thanks for the link! The math is a bit over my head, but this formulation seems more difficult than the one I'm familiar with. For example, x^2 is represented as 0::0::2 instead of 0::0::1 (because 2! = 2) and x^3 is represented as 0::0::0::6 instead of 0::0::0::1 (because 3! = 6). Is there a benefit to that?
- barrenko 2y agoI was introduced to the notion of power series two weeks ago, and now it's seemingly everywhere...
- coliveira 2y agoPower series have been everywhere for 200 years!
- barrenko 2y agoI am learning that indeed.
- angra_mainyu 2y agoPower series are possibly one of the most powerful tools in analysis.
- fp64 2y agoThis is one of the reasons I hate python, it allows for so many weird things that are only borderline standardised, if at all. When I started with python decades ago, I was also all hype, list comprehensions and functional patterns everywhere, and why not monkey patch a third party library on runtime? Now I regularly see such code written by the new junior devs, painful to maintain, painful to integrate into a larger code base, most certainly failing with TorchScript, and plainly convoluted for the sake of appearing smart. If you want to code it Haskell, have you considered using Haskell?
- sanderjd 2y agoThere is nothing weird or "borderline standardized" about generators...
- fp64 2y agoFair point, they have a proper PEP. I’ve been burnt by `lst[::-1]` and the likes which TorchScript does not (did not?) support and to my surprise learned that the standard is not clear on this behaviour. Generators are fine but I somewhat fail to see their use as they move state somewhere not really clear and I have seen plenty of problems with needing `list(generator())` confusing people
- ltbarcly3 2y agoI wrote a whole response thinking you were like 19 but you say you started with Python decades ago. If you still haven't gotten the hang of it I don't think there's anything to say. Generators have been in python for more than 2 decades so I find your claims extremely suspect.
- fp64 2y agoI’m using python since version 1 my friend, no need to get personal and insulting. I gave an example of something that is not defined properly in a PEP as well. I really do not like python, and one of the reasons is that is encourages writing of what I believe is code that is harder to understand and harder to maintain for people who didn’t write it. There’s nothing wrong with Haskell, but it has a rather steep learning curve when you’re not coming from functional programming and if you embrace patterns like this you put extra burden on your colleagues.
- notpushkin 2y agoAbsolutely unrelated, but there’s a Haskell-like syntax for Python: https://web.archive.org/web/20241205024857/https://pyos.github.io/dg/ https://web.archive.org/web/20241205024857/https://pyos.gith... f = x -> raise if x :: int => IndexError x otherwise => ValueError x Complete with pipelines, of course: "> {}: {}".format "Author" "stop using stale memes" |> print
- sizeofchar 2y agoWow, this is so awesome! A shame it didn’t progress.
- agumonkey 2y agonot too far from that there's https://coconut-lang.org/ https://coconut-lang.org/
- sanderjd 2y agoThis is certainly neat. This isn't a criticism, but I think more like an expansion on the author's point: The reason this all works is that generators plus memoization is "just" an implementation of the lazy sequences that Haskell has built in.
- mark_l_watson 2y agoWell, definitely very cool, but: the Haskell code is delightfully readable while the Python code takes effort for me to read. This is not meant as a criticism, this article is a neat thought experiment.
- abirch 2y agoI agree that this was a great article. For me, this isn't intuitive. It works; however, it doesn't scream recursion to me. def ints(): yield 1 yield from map(lambda x: x + 1, ints()) I preferred def ints(): cnt = 1 while True: yield cnt cnt += 1
- thyrsus 2y agoI'm a python newby, so please correct the following: The first function looks quadratic in time and linear in stack space, while the second function looks linear in time and constant in stack space. Memoizing would convert the first function to linear in time and linear in space (on the heap instead of the stack). For python, wouldn't I always use the second definition? For Haskell, I would use the [1..] syntax which the compiler would turn into constant space linear time machine code.
- trealira 1y agoLate response, but yes, you are completely right. You wouldn't use either implementation whether you're using Python or Haskell; you'd use what you said, because that's both the most obvious and the most performant method of achieving the goal. It's just a fun exercise to show that the version using a lazy map is equivalent to the obvious thing. Some people find it mind-bending and satisfying in the same way some people might find bit-twiddling hacks cool, or math nerds might find any of these mathematical identities cool. https://math.stackexchange.com/questions/505367/collection-of-surprising-identities-and-equations https://math.stackexchange.com/questions/505367/collection-o...
- louthy 2y agoI've never written a line of python in my life, so I'm interested in how this "recursive function" can do anything different on each recursive call if it takes no arguments? def ints(): yield 1 yield from map(lambda x: x + 1, ints()) Surely it would always yield a stream of `1`s? Seems very weird to my brain. "As simple as that" it is not!
- rowanG077 2y agoSure it yields 1. But then it adds one to each yield form the recursive call. And repeat.
- lalaithion 2y agoThe first item yielded from ints() is 1. For the second item, we grab the first item from ints(), and then apply the map operation, and 1+1 is 2. For the third item, we grab the second item from ints(), and then apply the map operation, and 1+2 is 3.
- louthy 2y agoGot it, thanks! The syntax was throwing me a bit there.
- lalaithion 2y agoIt’s a pretty bad system since it takes O(n^2) time to produce n integers but ¯\_(ツ)_/¯. Haskell avoids the extra cost by immutable-self-reference instead of creating a new generator each time.
- tomsmeding 2y agoEDIT: Wrong, the Haskell code is linear. See child comments. The extra cost is not in the recursive calls, of which there is only one per returned number. The cost is in achieving a yielded value n by starting with 1 and adding 1 to it (n-1) times. The given Haskell code: ints = 1 : map (+1) ints has the exact same problem, and it's just as quadratic. It might have a somewhat better constant factor, though (even apart from Python being interpreted) because there's less function calls involved.
- cartoffal 2y ago> The $ operator is nothing but syntactic sugar that allows you to write bar $ foo data instead of having to write bar (foo data). That’s it. Actually, it's even simpler than that: the $ operator is nothing but a function that applies its left argument to its right one! The full definition is f $ x = f x (plus a directive that sets its precedence and association)
- IshKebab 2y agoThis kind of thing is emblematic of how little Haskellers care about readability and discoverability. I'm sure it's great for people that are already expert Haskellers but it adds yet another thing to learn (that's really hard to search for!) and the benefit is... you can skip a couple of brackets. Awesome.
- yoyohello13 2y agoYou can type `:doc $` in ghci and it will tell you how the function is defined and give you usage examples.
- gymbeaux 2y agoWe aren’t enthusiastic about having to do that either
- yoyohello13 2y agoWhy? It’s the official documentation. Most haskellers have gchi open at all times when coding.
- gymbeaux 1y agoBecause (as far as I know) that's not the case for most other languages. It's an interesting (and certainly not necessarily "wrong") way to program when you aren't sure about how to write something. Python has something similar, that I will occasionally utilize for this purpose, but it doesn't show documentation (as far as I know) or anything like that while it sounds like gchi has functionality kind of like CLI tools' "--help"? With C#, intellisense takes the role of gchi and does pop up say the valid methods and properties of a class, and iirc includes documentation text. So it's less about that haskell has "coding help" built in and more about how that's presented to and interacted by the developer.
- ltbarcly3 2y agoI have no idea why this is even slightly neat? Like, it's not surprising that it works, it's not clever, it's not performant, and you can't actually code like this because it will overflow the stack pretty quickly. It doesn't even save lines of code. Alternatives that aren't dumb: for x in range(2**256): # not infinite but go ahead and run it to the end and get back to me from itertools import repeat for x, _ in enumerate(repeat(None)): # slightly more annoying but does work infinitely Granted these aren't clever or non-performant enough to excite functional code fanboys.
- mrkeen 2y agoThe functional fanboys have moved onto languages that don't overflow the stack.
- dev360 2y agoSo many people neglect the lack of TCO in Python, it’s like a beginner mistake you make when you try to make your Python read like the Haskell you just learned. Not sure why you’re getting downvoted though.
- nexo-v1 2y agoI really like this idea too. Generators are one of my favorite parts of Python — super memory efficient, and great for chaining transformations. But in practice, I’ve found they can get hard to reason about, especially when you defer evaluation too much. Debugging gets tricky because you can’t easily inspect intermediate states. When working with other engineers, I’ve learned to be careful: sometimes it’s better to just materialize things into a list for clarity, even if it’s less “elegant” on paper. There’s a real balance between cleverness and maintainability here.
- pletnes 2y agoThere’s some stuff in itertools to cut sequences into batches. Could be a useful intermediate step - grab 100 things at a time and write functions that receive and emit lists, rather than generators all the way down.
- ankitml 2y agoIt is possible to test the chaining though, if you know your data well. If not, those edge cases in the data quality can throw things off balance very easily.
- UltraSane 2y agoI often will intentionally store intermediate values in variables rather than just doing a clever one liner in Python specifically because I know it will make debugging easier.
- curtisszmania 2y ago[dead]
- benrutter 2y agoI like this! Tiny question, is the cache at the end any different from the inbuilt functools cache?