Earlier quoted context omitted.
Agreed, cyclomatic complexity is definitely something to be aware of when designing functional programming systems. Although, I disagree about Clojure's readability, but that's probably because I've been doing it for so long. Interesting Java project you've got there. Reminds me of Clojure's core.async library and software transaction memory ;) (though, I know it's not exactly the same thing). If I was transforming l…
I am really interested by Software transactional memory and enjoyed reading the Joe Duffy's blog posts about Midori and adventures with STM. https://github.com/joeduffy/joeduffy.github.io/blob/master/_... I like left-right concurrency control because it sidesteps a number of thread safety problems by ensuring that a thread can always safely read or write to its own buffer.
Mindset shifts for functional programming (with Clojure)
91–100 of 100 posts
Re: Mindset shifts for functional programming (with Clojure)
#92Earlier quoted context omitted.
Looping, as in using a loop keyword, is synchronous. Recursion is also a loop, but can iterate asynchronously as necessary. That is the primary advantage.
Recursion doesn't do that by default, though. And if you have to alter your code to run recursive fns on a new thread or executor, nothing prevents you from doing something similar with a loop, either.
If it would you can find me on IRC and I would be more than happy to show you how to program.
Re: Mindset shifts for functional programming (with Clojure)
#93Earlier quoted context omitted.
Recursion doesn't do that by default, though. And if you have to alter your code to run recursive fns on a new thread or executor, nothing prevents you from doing something similar with a loop, either.
Recursion does do that by default respective to what the given function executes. No alteration or special convention is required. Loops that require use of syntax other than a function call are synchronous only. If it would you can find me on IRC and I would be more than happy to show you how to program.
In most languages, recursion is not asynchronous, even if it could be, which is part of the point I was making. I'm guessing English is not your first language.
Re: Mindset shifts for functional programming (with Clojure)
#94Earlier quoted context omitted.
for(range(n)): is_true = !is_true And adjust for all the off by 1 errors. You're managing the same amount of state both ways, but with the loop all the state mutation lives on one line instead of spread throughout a stack.
That's not a "direct analogue" of my implementation, for many reasons; you've changed the semantics and engineering tradeoffs so much that we might as well write `n % 2 == 0`. The biggest problem is that, assuming we copy your snippet into a couple of function definitions, we've completely lost the encapsulation/separation-of-concerns/delegation/etc. provided by my `odd` and `even` functions; i.e. all of the "softwar…
> you've changed the semantics and engineering tradeoffs so much that we might as well write `n % 2 == 0`.
That is a fairly glaring weakness of the example you've chosen - it is taking a simple situation and overthinking it. It doesn't really matter because I see your point and surely one would exist, but do you have an example where this technique is an efficient solution?
Re: Mindset shifts for functional programming (with Clojure)
#95Earlier quoted context omitted.
Many of the replies to this comment seem to have focused on the problem domain (numbers and booleans); and missed the main feature I was trying to show about recursion, which is a collection of functions delegating sub-tasks between themselves (rather than e.g. trampolining via a "main loop") A closer analogy to my code would be something like this: the domain logic is still abstracted and encapsulated into separate…
I think the underlying claim by the OP is that they don't really run into situations in practice that need this kind of mutual recursion. Certainly, mutual recursion is not at all necessary for this boolean example; you argue that the obvious imperative form is not a direct analogue, but it's not really clear in what situation a direct analogue is desirable in the first place.
As mentioned in my comment, this is applicable wherever we have a "main loop". For example, a game might have a bunch of separate functions/methods to handle the various parts of the game; and a "main loop" which passes the return values of some as arguments to others:
function main() {
world = initWorld()
while(true) {
if (quitPressed()) break;
delta = calculateVelocities(world)
world = updatePositions(delta, world)
collisions = findCollisions(world)
world = handleEvents(mkEvents(collisions), world)
}
print("Goodbye")
quit()
}
Instead of passing data indirectly, by returning to the "main loop"; we could instead have those functions pass their results directly into whatever comes next. For example: function main() { gameStep(initWorld()) }
function gameStep(world) {
if (quitPressed()) quit(print("Goodbye"))
else worldStep(world)
}
function worldStep(world) {
delta = calculateVelocities(world)
updatePositions(delta, world)
}
function updatePositions(delta, world) {
newWorld =
collisions = findCollisions(newWorld)
handleEvents(mkEvents(collisions), newWorld)
}
function handleEvents(events, world) {
newWorld =
gameStep(newWorld)
}
This is a lot more complicated than the even/odd example, but it's the same pattern. In this case, it's probably clear why we wouldn't want to in-line all of the calculations into one gigantic loop, mixing up physics simulation, collision detection, input handling, combat system, and whatever else this game does.Again, not saying this is better/worse than a "main loop" (e.g. it can be helpful to have a "central location" to prevent spaghetti code); but (a) this style isn't possible without tail-call elimination, and (b) those who don't have tail-call elimination maybe wouldn't consider such an implementation.
Re: Mindset shifts for functional programming (with Clojure)
#96Earlier quoted context omitted.
That's not a "direct analogue" of my implementation, for many reasons; you've changed the semantics and engineering tradeoffs so much that we might as well write `n % 2 == 0`. The biggest problem is that, assuming we copy your snippet into a couple of function definitions, we've completely lost the encapsulation/separation-of-concerns/delegation/etc. provided by my `odd` and `even` functions; i.e. all of the "softwar…
I see, so the point you wanted to draw was that if you wanted to implement two functions, mutual recursion might let you separate the implementation logic. That is a nice trick. > you've changed the semantics and engineering tradeoffs so much that we might as well write `n % 2 == 0`. That is a fairly glaring weakness of the example you've chosen - it is taking a simple situation and overthinking it. It doesn't really…
I gave a more complicated, realistic example in a sibling comment https://news.ycombinator.com/item?id=35466256
Re: Mindset shifts for functional programming (with Clojure)
#97Earlier quoted context omitted.
I agree with your sentiment about recursion. It took me a damn long time to get used to it. But that's why I call it a mindset shift ;). If the codebase was written in Haskell, then there'd be no looping. Clojure is a bit odd in this case as it has a form called "loop" but it's really just a let binding over a fn, providing a point for `recur` to, well, recur to.
Again, the ultimate goal is to make the code readable (without sacrificing too much other qualities like performance). When you have a language and context that makes recursion easier to understand than loop -- go for recursion. One other reason to go for loops is to make sure you control the stack. With recursion it is not always immediately clear that the loop is going to get tail call optimisation. And good 9/10th…
And if you can manage to avoid allocating a list by recursing instead, you do save memory. (But I find it hard to think of such a case, as you can usually just loop over an iterator/generator instead?)
Re: Mindset shifts for functional programming (with Clojure)
#98Earlier quoted context omitted.
It doesn't make any sense to convert any arbitrary recursion into tail called optimized. If the recursion can be tail called optimized then yes the loop is the optimized performant implementation. But if the recursion fundamentally utilizes the call stack then the reverse is actually true. The recursion is now the performant implementation of a for loop. So a loop conversion optimization actually doesn't make sense h…
> The only advantage of the for loop in this case is that there won't be stack overflow, but overall the recursive version will actually be faster. In languages, which have realized the value of recursion, a stack overflow does not happen. For example: https://docs.racket-lang.org/guide/Lists__Iteration__and_Rec...
Re: Mindset shifts for functional programming (with Clojure)
#99Earlier quoted context omitted.
I think the underlying claim by the OP is that they don't really run into situations in practice that need this kind of mutual recursion. Certainly, mutual recursion is not at all necessary for this boolean example; you argue that the obvious imperative form is not a direct analogue, but it's not really clear in what situation a direct analogue is desirable in the first place.
> I think the underlying claim by the OP is that they don't really run into situations in practice that need this kind of mutual recursion As mentioned in my comment, this is applicable wherever we have a "main loop". For example, a game might have a bunch of separate functions/methods to handle the various parts of the game; and a "main loop" which passes the return values of some as arguments to others: function ma…
Sure, this is an example where tail calls work as well as a central loop to solve the problem. But OP was looking specifically for a situation where recursion has an active "sustained advantage over looping", i.e., where the solution can be expressed far more naturally through recursion than through looping. Failing that, favoring recursion can just be boiled down to the peculiar preferences of the FP zeitgeist. (That is, even if the language does have tail-call elimination, that doesn't automatically make recursion the better solution.)
> In this case, it's probably clear why we wouldn't want to in-line all of the calculations into one gigantic loop, mixing up physics simulation, collision detection, input handling, combat system, and whatever else this game does.
I don't see what that has to do with loops vs. recursion. In real-world imperative code, we'd break up our main loop into calls to separate stages of the cycle, then break up each stage into calls to different encapsulated submodules, etc.; we wouldn't need to make the loop a 100k-line wall of code running every tiny step. (Compilers can do that part on their own.) Do you mean that this mutually-recursive style can assist with encapsulation in some particular way?
Re: Mindset shifts for functional programming (with Clojure)
#100The biggest problem of functional languages is that they teach things of doing things that are not how computers work internally. For that C is much better. Assembly language the best. Or any other procedural language will teach you more about how computers run under the hood. Recursion is a purely math concept, in real life the chips don't work with recursion, they work strictly with if-then (more current/less curre…
it depends on your problem domain. Recursion makes some things easier - much easier than iteration. Try solving tower of hanoi without using recursion and see how hard it is: https://www.geeksforgeeks.org/iterative-tower-of-hanoi/
Or try writing a merge sort algorithm without using recursion.