[HN Gopher] Haskelling My Python
       ___________________________________________________________________
        
       Haskelling My Python
        
       Author : barrenko
       Score  : 133 points
       Date   : 2025-04-18 13:45 UTC (3 days ago)
        
 (HTM) web link (unnamed.website)
 (TXT) w3m dump (unnamed.website)
        
       | whalesalad wrote:
       | Generators 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 wrote:
       | Excited to read their next blog where they discover
       | functools.partial and return self.
        
         | nickpsecurity wrote:
         | I just discovered one from your comment. Thank you!
        
       | BiteCode_dev wrote:
       | Or, you know, use numpy.
        
         | shash wrote:
         | Yeah, 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 wrote:
       | This 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&d...
       | 
       | [1]: https://github.com/brianberns/PowerSeries
        
         | abeppu wrote:
         | Huh, 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
        
           | brianberns wrote:
           | These 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 wrote:
         | I was introduced to the notion of power series two weeks ago,
         | and now it's seemingly everywhere...
        
           | coliveira wrote:
           | Power series have been everywhere for 200 years!
        
       | fp64 wrote:
       | This 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 wrote:
         | There is nothing weird or "borderline standardized" about
         | generators...
        
           | fp64 wrote:
           | Fair 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 wrote:
             | I 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 wrote:
               | I'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.
        
               | IshKebab wrote:
               | I also really dislike Python but actually their
               | implementation of generators is pretty good IMO. Few
               | languages have it, e.g. Rust _still_ doesn 't have
               | generators.
               | 
               | They are pretty niche but when you find a situation that
               | needs them they're really elegant. I've used them for
               | generating deterministic test stimulus and it's really
               | nice.
        
               | fp64 wrote:
               | That's maybe my point, maybe not. Generators can be
               | useful, but somewhat niche as you said. I still prefer
               | explicit code and try to avoid generators, I can't
               | remember the last time I wrote a yield. I disagree on
               | their elegance. From my earlier days in python I remember
               | writing a lot of convoluted things that felt smart at the
               | time but turned out stupid, and I think representing
               | infinite list with recursive generators in python is
               | exactly that. Maybe a fun exercise to show what's
               | possible, but from painful experience I am quite certain
               | someone will read articles like these and apply it
               | everywhere without fully understanding it. I loved perl
               | and how terse and expressive I could be, but after a
               | little while of not using it (hah, python came to my
               | life!) I had no clue anymore what my code was supposed to
               | mean, and the same happened later to my "oh so elegant"
               | python code eventually.
               | 
               | Nowadays I mostly work with python ML code that has to be
               | exported as TorchScript, thus I'm very sensitive to
               | things that don't work there. Not per se a problem with
               | python - but having rewritten a lot of code to make it
               | work, pretty much each and every time I found the
               | explicit, imperative rewrite much cleaner and easier to
               | follow and understand
        
               | fp64 wrote:
               | I am not sure what your argument is supposed to be. That
               | I am wrong because I haven't gotten the hang of it after
               | all these years? I do understand generators. For a long
               | time. I still believe they are usually an anti-pattern,
               | same like massive abuse of inheritance where you have 20
               | classes but only a single instance but you made your code
               | "future proof".
               | 
               | The point I wanted to make is that using generator, in
               | particular like here, is something that I consider ugly
               | and difficult to maintain and it will probably have to be
               | rewritten when trying to export to TorchScript. I really
               | do not see how "just get a hang for it" can help me
               | reevaluate my perspective
               | 
               | Edit: Maybe you were hung up on my "standardised" - I
               | have to admit I do not know how thoroughly the PEP
               | defines generators and if really all edge cases are
               | defined without needing to check the python source code.
               | From past experiences, my trust in python language
               | standards is a bit shaky, as it had been difficult to
               | reproduce the very exact behaviour using a different
               | language, or python features - without requiring digging
               | through the sources.
        
       | notpushkin wrote:
       | Absolutely unrelated, but there's a Haskell-like syntax for
       | Python:
       | 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 wrote:
         | Wow, this is so awesome! A shame it didn't progress.
        
         | agumonkey wrote:
         | not too far from that there's https://coconut-lang.org/
        
       | sanderjd wrote:
       | This 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.
        
         | gerad wrote:
         | Isn't the original python without the recursion lazy as well? I
         | that was the entire point of generators.
        
           | sanderjd wrote:
           | You're right, fair enough. This isn't only about generators,
           | it's also about how they interact with recursion and
           | memoization.
        
       | mark_l_watson wrote:
       | Well, 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 wrote:
         | I 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
        
       | louthy wrote:
       | I'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 wrote:
         | Sure it yields 1. But then it adds one to each yield form the
         | recursive call. And repeat.
        
         | lalaithion wrote:
         | The 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 wrote:
           | Got it, thanks! The syntax was throwing me a bit there.
        
             | lalaithion wrote:
             | It's a pretty bad system since it takes O(n^2) time to
             | produce n integers but -\\_(tsu)_/-. Haskell avoids the
             | extra cost by immutable-self-reference instead of creating
             | a new generator each time.
        
               | tomsmeding wrote:
               | EDIT: 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.
        
               | mrkeen wrote:
               | When in doubt, measure!
               | 
               | Your code didn't show a quadratic blowup in the timing:
               | main = print . sum $ take 1000000 ints       ints = 1 :
               | map (+1) ints            500000500000       real
               | 0m0.022s       user    0m0.021s       sys     0m0.000s
        
               | tomsmeding wrote:
               | Interesting! My intuition was wrong. I neglected to fully
               | appreciate that the list is memoised.
               | 
               | What's happening, if I'm not mistaken, is that the
               | unevaluated tail of the list is at all times a thunk that
               | holds a reference to the cons cell holding the previous
               | list item. Hence this is more like `iterate (+1) 1` than
               | it seems at first glance.
        
       | cartoffal wrote:
       | > 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 wrote:
         | This 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 wrote:
           | You can type `:doc $` in ghci and it will tell you how the
           | function is defined and give you usage examples.
        
             | gymbeaux wrote:
             | We aren't enthusiastic about having to do that either
        
           | gizmo686 wrote:
           | Skipping brackets is incredibly useful for readability.
           | Basically every language that relies on brackets has settled
           | on a style convention that makes the redundant with
           | indentation. Admittedly, the big exception to this is
           | function application. But Haskell is much more function
           | oriented, so requiring brackets on function application is
           | much more burdensome than in most languages.
           | 
           | As to searchability, this should be covered in whatever learn
           | Haskell material you are using. And if it isn't, then you can
           | literally just search for it in the Haskell search engine
           | [0].
           | 
           | [0]
           | https://hoogle.haskell.org/?hoogle=%24&scope=set%3Astackage
        
           | sabellito wrote:
           | I agree with this sentiment. The one thing I liked about
           | Clojure was the simplicity of the syntax, as long as you kept
           | away from writing macros.
           | 
           | In general, after so many decades programming, I've come to
           | dislike languages with optional periods (a.foo) and optional
           | parenthesis for function calls - little gain to a confusion
           | of precedence, what's a field vs method. Seems that the whole
           | DSL craze of 15 years ago was a mistake after all.
           | 
           | Having said all that, I think haskell is awesome, in the
           | original sense of the word. I became a better programmer
           | after working with it for a bit.
        
           | lgas wrote:
           | You're only new once, but you write lines that benefit from $
           | every day.
        
             | IshKebab wrote:
             | That argument values learnability and discoverability at 0.
        
           | mrkeen wrote:
           | Excessive brackets are an evergreen complaint against lisp.
           | 
           | Here's one that made the front page, same time as your
           | comment: https://news.ycombinator.com/item?id=43753381
        
         | pklausler wrote:
         | In Haskell, ($) = `id`, because "id f x" = "(id f) x" = "f x".
        
       | ltbarcly3 wrote:
       | I 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 wrote:
         | The functional fanboys have moved onto languages that don't
         | overflow the stack.
        
           | ltbarcly3 wrote:
           | Read the article?
        
           | hedora wrote:
           | TIL haskell is Turing complete but only uses bounded memory.
        
       | nexo-v1 wrote:
       | I 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 wrote:
         | There'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 wrote:
         | It 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.
        
       | benrutter wrote:
       | I like this! Tiny question, is the cache at the end any different
       | from the inbuilt functools cache?
        
       ___________________________________________________________________
       (page generated 2025-04-21 23:01 UTC)