In most languages, the usefulness of for loops is almost entirely contingent upon either mutability (in the form of some accumulating variable and/or explicit alteration of program state) or on side effects like I/O routines. Partly, these things are often only necessary because the language itself doesn't handle recursion well; why use a for loop to generate a factorial, for example, when I can simply call the function again?
However, you can get something an awful lot like a traditional for loop, while still staying relatively functional pure: in Racket and Heresy, for loops are actually just some familiar syntax sugar layered on top of what's actually a recursive function. They actually become an abstraction over a very common pattern in Lisp languages: the let loop.
In Racket, often times I still need to accumulate some value over successive computations, but I don't necessarily need to build a whole named function to do it. In those scenarios I can make a let loop, which looks like this:
(let foo ([var 0]
[l '()])
(if (> var 10)
l
(foo (+ 1 var) (cons var l))))
If that looks a lot like a for loop, well, that's because it basically is! It's just the recursive, functional method of writing a similar pattern. Rather than having to rewrite that pattern over and over again in our code, Racket provides a whole range of built-in for loop styles, so that the same pattern can more simply be written: (for/list ([x (in-range 10)])
x)
Underneath, it's still the same functional code (well, with a lot of other toys to allow things like breaking on a condition, skipping entries, etc.). Heresy goes a step further towards simplifying the huge breadth of specialized for/functions that Racket uses, allowing you to write a for loop in a functional style while explicitly defining your carrying values. So you can write stuff like this: (for (x in (range 1 to 10) with '())
(carry (join x cry)))
Which seems more complicated, except that you can use that 'with' keyword to define any starting point for the 'cry' variable you like, meaning you can easily write all manner of different patterns with the same basic syntax.There are of course complications with this; the functional for loop is great in dynamically typed languages but it can be tricky to correctly type-check, which is why in Haskell it's a monad-y thing, and in Typed Racket there's a number of bugs still being worked out for handling the for loops.
Still, the two aren't necessarily mortal enemies, or explicitly imperative or functional, it just takes some thinking about what you wish to do.