
I thought I understood recursion - bendiksolheim
https://functional.christmas/2019/18
======
baq
> My background is in OO programming, mostly using C#. C# being the versatile
> language it is, I have had the perception that whatever you do in other
> programming languages, you can with a little more code and hassle achieve in
> C# as well. If need be, I can program C# using a functional paradigm. And,
> of course I use recursion all the time. I know all there is to know about
> recursion.

IME there are two kinds of programmers: ones who learned a single language
deeply and have almost religious confidence that it's the best thing ever,
even if they try to approach other languages with an open mind (i don't blame
them, i was like that a couple decades ago, too) and some who understand that
tools are just that - tools, which you should pick accordingly to the problem
you want to solve, because the other way ends up in a tail-wags-dog situation.
the blog post sounds like the author has realized that and started a journey
from the first type to the second type, which is to be commended. good job.

~~~
gear54rus
So if you consider some stack to be inferior (maybe because its highly
inconsistent in its design or it only runs on closed and locked down platforms
or whatever) you still should just dismiss it as 'oh its just a tool' instead
of accepting that it's shitty and makes you miserable when working with it?
Why wouldn't I want to work with best thing ever if it helps me keep my sanity
every day?

Do you want to use something that makes you miserable? Is any language
including those designed by 3 year old 'just a tool' that is to be used
despite obviously being shitty?

Never understood that argument.

~~~
roenxi
Maybe you should look within yourself and fix up whatever part of your
personality is making you miserable? I'm not being flippant. Programming
languages don't make people miserable.

There isn't really an argument, just an observation that the most efficient
way of thinking about programming is in data structures and algorithms which
are mostly language independent. Nobody should be spending most of their time
leveraging language features, so it isn't critical to use any particular
language.

I have a large range of pens of different quality. I have a favourite pen. But
I'm willing to use any pen if I need to write something down. My focus is on
composing a message, not on worrying about the quality of my calligraphy or
the occasional scratchy mark. If 'm going to be writing all day I'd rather
have my favourite pen but any one will do.

~~~
kalenx
Saying a language makes you "miserable" does not forcibly mean you literally
want to kill yourself because of it.

I can totally see myself having more or less fun executing the exact same
programming job, depending on the language I'm asked to used. And yes, to some
extent, some projects (especially the ones where you have to start from an old
codebase in a deprecated and ancient language) make me "miserable". That
doesn't mean I'll quit my job on the spot.

~~~
jiggawatts
I agree.

I used to be a C++ programmer. I was _good_ at it. I could do advanced
template magic. I understood multiple inheritance and used it correctly, for
the right purpose. I literally had dreams in C++.

Then I started using Java, and realised that I could be 5x as productive with
far fewer headaches chasing down yet another linker error with some
unintelligible error message.

What do you _mean_ __malloc is undefined!? How could it _possibly_ be missing?
It was there a minute ago! I'm linking the standard library already, so what
exactly do you want me to _do_ about it, Mr C++ Compiler!?

Then I discovered IntelliJ IDEA, and I swear that I felt like the skies had
opened up, there was a beam of light shining upon me, my spirits were lifted,
and I could hear the voice of God saying: "Fear not! For now you can make
changes globally and ye can rest assured that this will not break thy code!"

And then along came C#, which was just-like-Java, but with another 2x boost in
productivity from all the built-in stuff that Java was missing, the language
niceties, and the removal of the boilerplate. It has made me _so happy_ over
the years. Nearly two decades of _effortless productivity_.

I mean, seriously, how ridiculously awesome and simple is the "async"
keyword!? It's like _magic_! I love it. LINQ is neat. The debugger is awesome.
IntelliTrace is nifty. I could go on.

At one point recently I was forced to use C++ to write just a few hundred
lines of code. It was _physically painful_. The language is a quagmire of
footguns. The code I wrote was trivial string processing code, yet despite
using only std::string it _still_ somehow managed to crash.

I decided right then and there that I'm never going back to C++. Never! It's
not worth it. My sanity, nay, my very soul deserves better.

------
cousin_it
Using functional programming to find primes is tricky:

[https://www.cs.hmc.edu/~oneill/papers/Sieve-
JFP.pdf](https://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf)

Melissa O'Neill shows that the typical FP sieve has worse big-O complexity
than imperative.

[https://wiki.haskell.org/Prime_numbers#Sieve_of_Eratosthenes](https://wiki.haskell.org/Prime_numbers#Sieve_of_Eratosthenes)

The Haskell wiki gives several sieve implementations and can't figure out the
big-O complexity. (The imperative sieve is O(n log log n).)

------
aurbano
This was such a nice read! I've only ever written Java & JavaScript really,
and Haskell is always at the top of the list of things I want to learn!

When I got to the 7 line snippet at the end I was blown away by the elegance,
and the fact that I couldn't really understand exactly what each thing did. So
I decided to spend a few hours going through it character by character and
documented my process here [1] in case it was useful to anyone else.

I'll continue by reading a book on Haskell, but open to any advice from anyone
on good ways to get into it!

[1] [https://aurbano.eu/note/2019-12-18-journey-into-
haskell/](https://aurbano.eu/note/2019-12-18-journey-into-haskell/)

~~~
mettamage
Fun blog post! I know nothing about Haskell as well.

Quite casual, just what I needed. Thanks for writing it up!

One blogging tip (note: the rest was perfect): if you would've chopped the
following in 2 or 3 lines, then the casual style would be perfect.

> primes n = take n $ sieve [2..] where sieve (p:xs) = p : sieve [x | x <\-
> xs, x `rem` p > 0] sieve [] = []

I feel that it'd be really hard to do though.

~~~
aurbano
Thanks! And good idea, I just updated it breaking that down a bit more :)

------
d--b
C# equivalent:

    
    
        IEnumerable<int> filter(int p, IEnumerable<int> xs) {
         foreach(var j in xs)
          if (j % p > 0) yield return j;
        }
        
        IEnumerable<int> sieve(IEnumerable<int> s) {
         var p = s.First();
         yield return p;
         foreach(var e in sieve(filter(p,s.Skip(1))))
          yield return e;
        }
        
        var n = sieve(Enumerable.Range(2, 10000)).Skip(10).First();

~~~
m0skit0
Less readable in my opinion and I'm more used to C# code than Haskell.

~~~
jayrwren
I feel the opposite. I find the C# more readable than the Haskell. I haven't
written C# regularly in 10yrs.

------
tromp
The Haskell code can be slightly simplified to

    
    
        nth :: Integral a => Int -> Maybe a
        nth n | n < 1 = Nothing
              | True  = Just (primes !! (n-1))
    
        primes :: Integral a => [a]
        primes = sieve [2..]
          where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]
    

Note that this is not a genuine sieve [1]

[1] [https://www.cs.hmc.edu/~oneill/papers/Sieve-
JFP.pdf](https://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf)

------
martin1975
"It is rather an attempt to get my head around functional programming, and to
me Haskell doesn’t seem to have any practical application beyond that."

Cardano/ADA's core Ouroboros protocol was entirely written, with formal
proofs, in Haskell. It is by far the most serious attempt at proof of stake in
the crypto industry.

I think what you're really saying is, you won't find many jobs out there
w/Haskell as a requirement... but I wouldn't go as far as saying Haskell has
no purpose past teaching FP. It certainly is the poster child of FP, however
it is not without practical/industrial use. You just have to look harder to
see where it's being used.

~~~
javajosh
_> You just have to look harder to see where it's being used._

This is where advocacy slips over the line into a kind of blind faith
evangelism -- with an added pinch of pedantry peculiar to our field.

I will state without proof that every language ever invented is currently
being used for something practical somewhere. E.g. someone has a useful shell
utility they wrote in Brainfuck that they run every day and that they love.
This does not mean that BF has 'practical application' in the usual sense of
the phrase. In the same way, just because there is a real program written in
Haskell somewhere does not mean it has practical application.

 _IMHO_ a language (or a tool in general) should have a community of
practitioners that regularly _think in_ and _create with_ the tool, and a good
signal that that is happening are the number of open-source releases in that
language. I have never in my life installed a Haskell program, or known of
anyone installing one. (The exception being this one Haskell fan at JPL who
loved talking about the language and the lambda calculas, but to my knowledge,
never wrote anything real in it, and he just installed learning toys, not
utilities.)

All that said, there are some good examples of unpopular tools that enjoyed
success later in life. Quaternions lost to vectors historically, but graphics
programmers rediscovered their benefits and resurrected them. Maybe Haskell
will have it's time, but that time is not now.

~~~
leadingthenet
I'm not sure that holds up. Real people working at real companies are writing
real code in Haskell every day. I found several job postings in London that
advertised Haskell as a requirement / nice to have. Haskell is also taught and
used extensively at my alma matter, and is in fact the first language you will
be introduced to at a CS/SE degree.

How is that in any way, shape, or form comparable to Brainfuck?!

~~~
javajosh
When was the last time you installed and/or ran a Haskell program? If the
answer is more recent then "never", then what was it?

It's possible that Haskell is like COBOL in that it's used in industry but not
at all by the community. But I'm not sure how I would characterize it, then.
Does industry use count as "practical"? I honestly don't know. Anyway,
examples would be nice.

~~~
chii
And what is "the community"?

Haskell indeed has less penetration than something more popular like
JavaScript.

But the Haskell language is not easy to use if you're a script-kiddy. Most
"easy" languages can have some form of copy-paste reuse, and a lot of "the
community", it would seem, just append together useful snippets to get their
programs done (and whether they have a deep understanding or not is not
needed).

This isn't true for Haskell. It's actually quite hard to just "copy/paste"
Haskell without out knowing the underlying concepts. I wager that the reason
there seems to be less.of a community in Haskell is due to this property.

~~~
Hercuros
I think it is a bit of an oversimplification to say that Haskell is less
popular because it is not as easy to "copy/paste" together code for it. I
would say it also isn't that easy to copy/paste together C++ code, for
instance. This also conveys a message of "Haskell is only for smart people",
which I don't think is the right attitude.

------
pjc50
It looks much more functional if you get familiar with LINQ.

    
    
        var allPossibleNumbers = Enumerable.Range(3, max-3);
        var possiblePrime = allPossibleNumbers
                .AsParallel()
                .Where(n => Enumerable.Range(2, (int)Math.Sqrt(n))
                                      .All(i => n % i != 0)
                );
    

(from [https://codereview.stackexchange.com/questions/6115/sieve-
of...](https://codereview.stackexchange.com/questions/6115/sieve-of-
eratosthenes-in-c-with-linq) )

------
deepaksurti
One of the most beautiful introduction to recursion is in Chapter 8 using
dragon stories, of the `Common Lisp: A Gentle Introduction to Symbolic
Computation` book by David S. Touretzky. [1], is the link to the free pdf.

[1]
[https://www.cs.cmu.edu/~dst/LispBook/book.pdf](https://www.cs.cmu.edu/~dst/LispBook/book.pdf)

------
leibnitz27
Sure you can. Here's a super grotty (and expensive) c# version.

    
    
            static IEnumerable<int> infinite()
            {
                int x = 2;
                while (true)
                {
                    yield return x++;
                }
            }
    
            static IEnumerable<int> primes(IEnumerable<int> l)
            { 
                int head = l.First();
                yield return head;
                foreach (var x in primes(l.Skip(1).Where(x => x % head != 0)))
                    yield return x;
            }
    
    
            static void Main(string[] args)
            {
                System.Console.WriteLine(string.Join(",", primes(infinite()).Take(20).Select(x => x.ToString()))); 
            }

~~~
leibnitz27
(and yes, the nested yield is ABSOLUTELY an anti-pattern. They did promise to
do a bulk yield at some point, I haven't used C# in a few years so don't know
if I'm out of date!)

------
nradov
Depending on the particular language and platform, it can be quite dangerous
to use recursion in production software due to the risk of a stack overflow.
To be safe you have to first determine analytically that this can never
happen. For algorithms that manipulate tree data structures it's often safer
to avoid real recursion and instead sort of simulate recursion using a list or
stack data structure allocated on the heap. At least that gives you a better
opportunity to fail gracefully if the input data is too large to process
within your resource constraints.

~~~
setr
Tbf most code doesn't encounter trees deep enough to generate a stack
overflow.

It's more of a concern with things like mutual recursion (multiplying your
frame depth at each step, instead of just +1) or if your recursion depth is
based on unbounded user input (eg iterating over an AST, or this code snippet)

------
snak
Here's my code golf attempt in C#.

    
    
        public static List<int> Sieve(int n)
        {
            bool[] arr = Enumerable.Range(0, n).Select(i => true).ToArray();
    
            Enumerable
                .Range(2, (int) Math.Sqrt(n) - 2)
                .ToList()
                .ForEach(i => { if (arr[i]) Enumerable.Range(0, (n - (i * i)) / i).ToList().ForEach(j => arr[j * i + (i * i)] = false); });
        
            return arr.Select((b, i) => b ? i : 0).Where(i => i > 0).ToList();
        }

------
butterisgood
It’s the Sieve of Eratosthenes. Probably someone has written it in C#.

Here is a python version.

[https://www.google.com/amp/s/www.geeksforgeeks.org/python-
pr...](https://www.google.com/amp/s/www.geeksforgeeks.org/python-program-for-
sieve-of-eratosthenes/amp/)

~~~
edflsafoiewq
This is not the Sieve (despite its name). For just one reason, the Sieve does
not ever require testing divisibility.

~~~
edflsafoiewq
This is the equivalent Python:
[https://pastebin.com/CXfaNHPC](https://pastebin.com/CXfaNHPC)

------
jpeg_hero
I was thinking about this recently: is there ever a reason to put recursion in
an everyday “workman” code base?

Seems like it would be so out of place in a real industry code base, like a
infinite loop waiting to happen. There are always better more readable and
maintainable ways to accomplish the same thing.

~~~
Smaug123
Sure there is: it lets you express loops immutably.

Rather than `state := null; while condition do: mutate-state-and-recompute-
condition`, you can do `let loop(state) = if shouldContinue(condition) then
loop(newState) else resultOfTheLoop`. Rely on the tail-call optimiser to
compile this to a genuine imperative loop.

This looks very odd the first few times you see it, but it's much harder to
get wrong.

------
cjfd
To understand recursion you must first understand recursion.

~~~
martin1975
The first rule of recursion is we do not talk about recursion.

The second rule of recursion is we do not talk about recursion.

The third rule of recursion is without a base case, you have no recursion.

The fourth rule of recursion is it breaks you in two or more pieces.

~~~
BerislavLopac
The fifth rule of recursion is that it is not really recursion if it isn't
tail-call optimised.

~~~
tr352
So a recursive tree search is not really recursive?

~~~
BerislavLopac
Oh no, it is _really recursive_ \-- it's just not "real recursion" (TM). ;)

------
mbrodersen
Good developers can solve problems and be productive with any programming
language/tools. Bad developers can't solve anything unless they use the one
specific tool they know.

~~~
mbrodersen
Another way to put it is that if you always "use the best tool in the tool
box" then you better have more than just one tool in the tool box.

------
meigwilym
> This led me to understand how little I had understood.

The unknown unknowns become known.

------
vincnetas
Java one liner :

    
    
      Stream.iterate(1, i -> i + 1).filter(i -> !IntStream.range(2, i).filter(v -> i % v == 0).findAny().isPresent()).skip(100000).findFirst();

------
millstone
Ooh, what's the time and space complexity of `primes`?

~~~
cjfd
Clearly much worse than the original solution. Basically, a nested sequence of
filters is built, one to filter out multiples of each prime number. One way in
which the complexity of this is bad is that if you want prime numbers < N you
only need to filter out primes smaller than sqrt(N). It is telling that the
first solution contains an sqrt but not the second one....

These kind of functional solutions are very cute mathematically, but...

I once wrote this way of generating an infinite list of primes in unlambda.
That was kind of interesting to do.

~~~
merijnv
> One way in which the complexity of this is bad is that if you want prime
> numbers < N you only need to filter out primes smaller than sqrt(N). It is
> telling that the first solution contains an sqrt but not the second one....

That's not really true, though, because that filter is applied lazily, so it
only get evaluated up until the Nth prime and no further.

------
axilmar
I do not get what this publication is trying to say. Is Haskell better than
C#? are Haskell programs shorter than C#? is recursion difficult? is recursion
difficult in C#? I don't get it.

~~~
ColinWright
I can't tell if you're seriously enquiring, or simply being snarky. However,
taking your comment at face value ...

To me this post is saying that in some languages (in this case Haskell) there
are ways of working that are hard to emulate in other languages (in this case
C#). The post is talking through a specific example of this, and pointing out
that if you only know one language (in this case C#) then you might be missing
out on styles of thought that can help in solving certain problems.

The post isn't about one language being better or worse in absolute terms,
it's about broadening your range as a programmer so you are aware of other
techniques and styles of thought.

To go all "new age-y" it's about enlightenment. Usually Lisp is the language
used to help programmers achieve enlightenment, but other options exist, such
as fexl, Haskell, and others.

Seriously, many languages are "about the same," but some really do make you
think differently. You can be an excellent and productive programmer without
them, and the vast majority of programmers never try to step outside the
bubble of Imperative & OO languages, but you will be missing out if you don't
at some stage embrace one of these other "pure(ish) functional" languages that
is genuinely different.

YMMV

Some reading:

* [https://www.defmacro.org/ramblings/lisp.html](https://www.defmacro.org/ramblings/lisp.html)

* [https://stackoverflow.com/questions/2036244/whats-so-great-a...](https://stackoverflow.com/questions/2036244/whats-so-great-about-lisp)

Search for "Lisp Enlightenment" and cull mercilessly.

~~~
cannabis_sam
Thanks for a great explanation of a weird topic, that I’ve struggled to do put
into words!

I’ve had a few similar experiences in my humble programming education.

The first was at uni, having to learn c, c++ and python in one semester, after
only using java for the first two semesters. (And a tiny bit of php and visual
basic before that)

The second was exposure to scheme/racket and real functional programming.

The third time was the most amazing mix of haskell, type theory, lambda
calculus, logics, agda, category theory, proof theory, model theory and just
theoretical computer science in general.

It leaves you with this wonderful and strange view of programming, without any
of the concrete computational models.

------
twobat
What's up with the influx of christmas domains here on HN?

~~~
cstuder
It's a collection of advent calendars from a norwegian agency with fun TLDs:
[https://blogg.bekk.no/introducing-bekk-christmas-
ad01660ccad...](https://blogg.bekk.no/introducing-bekk-christmas-ad01660ccadf)

------
neilwilson
Can you cheat?

#!/usr/bin/env ruby require 'prime'

def find_prime(nth) Prime.lazy.drop(nth-1).first end

