Rich Hickey has a great description of how to think about working with immutable values - what we usually want when we change a variable is simply the next value and so long as we are getting the value we expected, there's no need to name it.
In other words if our current position in an array is 2, what we need to access the next position is the value 3. Creating a variable int i=2; and then mutating it i++; introduces the possibility of side effects. This not to say that at an abstraction layer below our programming language a register won't get incremented, only that our brains don't need to worry about the mechanisms most of the time if we use a functional language.
Functional programming has deep semantic implications. Calling it "syntactic sugar" is incorrect. Foreach in particular is arguably syntatic sugar, though, to be sure - functional or not.
> There's nothing magical about functional programming - it is no more than syntactic sugar to help programmers control the state of computing devices.
In the same way that objects in Java are just syntactic sugar for encapsulating state. In the same way that a compiler is just the machine that takes a language of the syntactic sugar of language A to the actual language A.
> Foreach is a higher level abstraction that makes reasoning about our code easier. The bits still change.
So how do you write a Foreach loop from a regular for-loop in your typical imperative language? How do you compose that Foreach loop to make even higher level abstractions like map, filter and fold/reduce?
> So how do you write a Foreach loop from a regular for-loop in your typical imperative language?
By walking through the collection in the update step of the for loop (for languages with less-general version of for than C and friends, this may actually require a while loop rather than a for loop.)
> How do you compose that Foreach loop to make even higher level abstractions like map, filter and fold/reduce?
By abstracting it behind a function call that takes a function (or function reference/pointer, or in particularly limited languages a location to jump the current instruction pointer to) as an argument.
Yes, languages that focus on the functional paradigm (or which have been influenced by it even if it isn't their central paradigm) have syntax which makes this more straightforward than it might be in some other languages,
In the case of foreach, it's simple to be sure, and I'm pretty fine with calling foreach "syntactic sugar". Even something as simple as function composition breaks down a bit in C. You can manage it, but you're either not really using C anymore (writing out machine code in memory and treating it as a function, which is going to be tremendously compiler and architecture specific) or you're not really dealing with C functions anymore (passing around a structure that stores the things to compose, "calling" that function with a separate "apply" function).
Yes, you can ultimately write all of Haskell in C and code at the higher level, exceedingly verbosely, but to say that's "just syntactic sugar" would be absurd - "fundamentally all these languages are Turing equivalent" already encapsulates that observation, there's nothing new the notion of "just syntactic sugar" is adding.
Typically I restrict "just syntactic sugar" for things that are simple transformations that can be done locally. The archetypical example being array syntax in C, which can be described more precisely by a syntactic transformation than a semantic operation:
Given:
char *c = "abcd";
int i = 2;
It turns out that:
a[i] = *(a + i)
but that's equivalent, by commutativity of +, to:
*(i + a)
and so, counter-intuitively if you're thinking of [] as a semantic "array indexing" operation, you get the same results with:
> In the case of foreach, it's simple to be sure, and I'm pretty fine with calling foreach "syntactic sugar".
Yeah, I was responding specifically to the "foreach" and specific things layered on top of foreach, which are fairly straightforward in most popular non-FP languages.
> Even something as simple as function composition breaks down a bit in C.
Quite. No argument there. At least with standard C. (I think Clang and GNU C both have extensions -- but not the same ones -- that make this reasonably straightforward in simple cases, but still much less elegant than, say, Haskell.)
For sure - and that is the strength of functional languages. Citing one example of functionality that, nowadays, most languages have built in doesn't make much of a point.
In other words if our current position in an array is 2, what we need to access the next position is the value 3. Creating a variable int i=2; and then mutating it i++; introduces the possibility of side effects. This not to say that at an abstraction layer below our programming language a register won't get incremented, only that our brains don't need to worry about the mechanisms most of the time if we use a functional language.