Live data from Hacker News

Mindset shifts for functional programming (with Clojure)

blog.janetacarr.com

41–50 of 100 posts

Re: Mindset shifts for functional programming (with Clojure)

#41

Earlier quoted context omitted.

> Nobody has yet convinced me that recursion has any sustained advantage over looping. Looping may require trampolining or defunctionalisation, whilst recursion can be written much more directly and simply. As a very simple example (in pseudocode): even(n: uint): boolean = n match { case 0: true case n: odd(n-1) } odd(n: uint): boolean = n match { case 0: false case n: even(n-1) } Whilst these are pretty silly implem…

It's heretic and beside the point, but: bool odd(uint n){return n&1;} bool even(uint n){ return !odd(n);} I can't help but think in terms of classic CPU.

    fun odd (n: Uint32) -> Bool =
        let Uint32(n0, _, _, _) = n in
        let Byte(b0, _, _, _, _, _, _, _) = n0 in
        b0
Who needs hardware support for integers anyway? If you want a two-complement arithmetic, you can build it yourself (although presumably it'll be in the standard library).

Re: Mindset shifts for functional programming (with Clojure)

#42
post #24

Earlier quoted context omitted.

> Recursion is also producing results that are "wrong answers" Not true. If you share your copy of: answer = sum [1..10] One viewer might see: answer = 3 + sum [3..10] and another viewer might see: answer = 6 + sum [4..10]. They may be "partial results", but they are correct and equal to each other. This is different from one viewer seeing 3 and another viewer seeing 6. "But can't you just hide the partial result unt…

You tend to not put temporary results of loops in instance variables, but in local variables. Which aren't visible externally at all. And yes, if you do have that exceptional case of a complex algorithm that needs to store partial results in ivars as they are required by different methods and inconvenient to pass around individually, those ivars tend to not be exposed. In fact, you'd more likely create a separate con…

> You tend to not put temporary results of loops in instance variables, but in local variables. Which aren't visible externally at all.

Cool! Then it follows:

If a result is not temporary, then it can be immutable. Properly immutable, not just final.

If a result is temporary, then it is not to be visibly external at all. Not as a field, and not via any getter.

I could live with that.

Re: Mindset shifts for functional programming (with Clojure)

#43
post #15

> Recursion over Looping Part of what makes Clojure a great programming language is that you don't have to believe this if you don't want to. Nobody has yet convinced me that recursion has any sustained advantage over looping. Using a loop is generally bad practice if a more specialised operation is available (don't loop if something is a simple map or reduce for example). But if the situation justifies a recursion t…

> Nobody has yet convinced me that recursion has any sustained advantage over looping. Looping may require trampolining or defunctionalisation, whilst recursion can be written much more directly and simply. As a very simple example (in pseudocode): even(n: uint): boolean = n match { case 0: true case n: odd(n-1) } odd(n: uint): boolean = n match { case 0: false case n: even(n-1) } Whilst these are pretty silly implem…

Here's how I would write the loop version of this. It does involve an optimization since we know parity only depends on n - 1.

  #include 
  
  template 
  bool even(T n)
  {
      bool is_even{true}, is_odd{false};
      for (; n--;)
      {
          std::swap(is_even, is_odd);
      }
      return is_even;
  }

Re: Mindset shifts for functional programming (with Clojure)

#44
post #18
post #15

> Recursion over Looping Part of what makes Clojure a great programming language is that you don't have to believe this if you don't want to. Nobody has yet convinced me that recursion has any sustained advantage over looping. Using a loop is generally bad practice if a more specialised operation is available (don't loop if something is a simple map or reduce for example). But if the situation justifies a recursion t…

I mostly agree, and proponents of FP shouldn't stress the recursion too much. When I program in Haskell, I rarely use recursion myself. Most loops are just maps and folds. Need to build another structure from existing structure? Foldl'. Once I understood that the value being folded can be arbitrarily complex, FP became simpler (but terser) than iterative programming for me.

I don't think of map operations as loops. Instead as parallel operations on elements of a list works as a better mental model for me.

When learning them coming from an imperative background, it's inevitable to start with that idea, but somehow feels like a limiting model.

Re: Mindset shifts for functional programming (with Clojure)

#45
post #24
post #8

Earlier quoted context omitted.

I don't understand where you come from with your argument. Recursion is also producing results that are "wrong answers" although I prefer to call them partial results. Just like a loop that is producing partial results as long as it has not finished yet.

> Recursion is also producing results that are "wrong answers" Not true. If you share your copy of: answer = sum [1..10] One viewer might see: answer = 3 + sum [3..10] and another viewer might see: answer = 6 + sum [4..10]. They may be "partial results", but they are correct and equal to each other. This is different from one viewer seeing 3 and another viewer seeing 6. "But can't you just hide the partial result unt…

Have you heard about the concept of "loop invariant"?

    s = 0
    i = 0

    // Invariant: before and after every iteration, s is equal to sum of [0..i]
    // End condition: i == n
    while i != n:
        s += i
        i += 1

    // Here, i == n and so s is a sum of [0..n] because the invariant still holds
That's how loops were supposed to be thought about (and written) since the 70ies, and it's more or less equivalent to induction which is what you're supposed to use when you think about recursive functions.

Re: Mindset shifts for functional programming (with Clojure)

#46
post #15

> Recursion over Looping Part of what makes Clojure a great programming language is that you don't have to believe this if you don't want to. Nobody has yet convinced me that recursion has any sustained advantage over looping. Using a loop is generally bad practice if a more specialised operation is available (don't loop if something is a simple map or reduce for example). But if the situation justifies a recursion t…

>Nobody has yet convinced me that recursion has any sustained advantage over looping.

Recursion gives you a stack by default. You don't have to explicitly think about the stack. In looping the stack must be explicit.

Recursion and looping are the same thing. Recursion can be mechanically translated to a for loop and a stack, the concepts are isomorphic.

Re: Mindset shifts for functional programming (with Clojure)

#47
post #42

Earlier quoted context omitted.

You tend to not put temporary results of loops in instance variables, but in local variables. Which aren't visible externally at all. And yes, if you do have that exceptional case of a complex algorithm that needs to store partial results in ivars as they are required by different methods and inconvenient to pass around individually, those ivars tend to not be exposed. In fact, you'd more likely create a separate con…

> You tend to not put temporary results of loops in instance variables, but in local variables. Which aren't visible externally at all. Cool! Then it follows: If a result is not temporary, then it can be immutable. Properly immutable, not just final. If a result is temporary, then it is not to be visibly external at all. Not as a field, and not via any getter. I could live with that.

That doesn't follow. Objects do (much) more than implement algorithms in loops.

In fact, I'd say that if you're doing algorithms as objects, you're almost certainly Holding it Wrong™.

Algorithms tend to be perfectly fine contained in a method of an object.

Re: Mindset shifts for functional programming (with Clojure)

#48
post #27

Earlier quoted context omitted.

I think the difference between recursion and looping is that the former requires you to be very explicit about the state you intend to modify and persist between iterations, while it's generally somewhat of a free-for-all in imperative loops.

The most common looping construct I see in clojure is the for statement Which isn't quite the free for all it is in traditional languages You have to be very conscious and coding against the grain to use it to bang on values in place

This is a general design principle that I appreciate about the Lisp family of languages in general. You can write highly imperative code that mutates things in-place, but it's a little harder to do and it's something you do only when you really need to do it. Whereas the default idioms are functional, and functional design is the path of least resistance. Perl is morally the opposite of functional programming in many ways, but I actually think this design is a great embodiment of the "make easy things easy, make hard things possible" ethos of Larry Wall.

Re: Mindset shifts for functional programming (with Clojure)

#49
post #15

> Recursion over Looping Part of what makes Clojure a great programming language is that you don't have to believe this if you don't want to. Nobody has yet convinced me that recursion has any sustained advantage over looping. Using a loop is generally bad practice if a more specialised operation is available (don't loop if something is a simple map or reduce for example). But if the situation justifies a recursion t…

Had GPT-4 clarify some of the syntax for me https://poe.com/s/uj80au2i4TuNWgUa1pNm

Re: Mindset shifts for functional programming (with Clojure)

#50
post #15

> Recursion over Looping Part of what makes Clojure a great programming language is that you don't have to believe this if you don't want to. Nobody has yet convinced me that recursion has any sustained advantage over looping. Using a loop is generally bad practice if a more specialised operation is available (don't loop if something is a simple map or reduce for example). But if the situation justifies a recursion t…

> Nobody has yet convinced me that recursion has any sustained advantage over looping. Looping may require trampolining or defunctionalisation, whilst recursion can be written much more directly and simply. As a very simple example (in pseudocode): even(n: uint): boolean = n match { case 0: true case n: odd(n-1) } odd(n: uint): boolean = n match { case 0: false case n: even(n-1) } Whilst these are pretty silly implem…

I'm not going to argue this is good, but it's fairly analogous:

    boolean isEven(int n) {
        boolean even = true;
        for (; n > 0; --n) even = !even;
        return even;
    }
Post reply on HN