Functional programming insists on telling what to do, rather than how to do.
For example,Scala's collections library has methods like filter, map etc. These methods enable developers to get rid of traditional for loops, and hence so called imperative code.
But what is so special about it?
All I see is a for loop related code encapsulated in various methods in a library. A team working in imperative paradigm could also ask one of its team member to encapsulate all such code in a library and all other team members can then use that library, so we get rid of all those imperative code. Does that mean the team has suddenly transformed from imperative to declarative style?
So first of all, functional programming and imperative programming are equivalent when it comes down to brass tacks, as shown by the Church-Turing theorem. Anything that can be done by one can be done by the other. So while I really prefer functional languages, I can't make a computer do anything that can't be done in an imperative language.
You'll be able to find all kinds of formal theories about the distinction with a quick google search so I'll skip that and try to illustrate what I like using some pseudocode.
So for example, let's say I have an array of integers:
var arrayOfInts = [1, 2, 3, 4, 5, 6]
And I want to turn them into strings:
function turnsArrayOfNumbersIntoStrings(array) {
var arrayOfStrings = []
for (var i = 0; i < arrayOfInts; i++) {
arrayOfStrings[i] = toString(arrayOfInts[i])
}
return arrayOfStrings
}
Later, I'm making a network request:
var result = getRequest("http://some.api")
That gives me a number, and I also want that to be a string:
function getDataFromResultAsString(result) {
var returnValue = {success:, data:}
if (result.success) {
returnValue.success = true
returnValue.data = toString(data)
}
return returnValue
}
In imperative programming, I have to describe how to do what I want. Those functions are not interchangeable because going through an array is obviously not the same as doing an if statement. So turning their values to strings is totally different, even if they both call the same toString function.
But the shape of those two steps is exactly the same. I mean if you squint a a little bit, they are the same function.
How they do it has to do with a loop or if statement, but what they do is take a thing that has stuff in it (either array with ints or request with data) and turn that stuff into a string, and return.
So maybe we give the things a more descriptive name, that applies to both. They are both a ThingWithStuff. That is, an array is a ThingWithStuff, and a request result is a ThingWithStuff. There is a function for each of them, generically called stuffToString, that can change the stuff inside.
One of the things functional programming has is first order functions: functions can take functions as arguments. So I could make it more general with something like this:
function requestStuffTo(modifier, result) {
var returnValue = {success:, data:}
if (result.success) {
returnValue.success = true
returnValue.data = modifier(data)
}
return returnValue
}
function arrayStuffTo(modifier, array) {
var arrayOfStrings = []
for (var i = 0; i < arrayOfInts; i++) {
arrayOfStrings[i] = modifier(arrayOfInts[i])
}
return arrayOfStrings
}
Now the functions for each type keep track of how to change their internals, but not what. If I want a function that turns an array or request of ints to strings, I can say what I want:
arrayStuffTo(toString, array)
requestStuffTo(toString, request)
But I don't have to say how I want it, because that was done in the earlier functions. Later, when I want array and request of say, booleans:
arrayStuffTo(toBoolean, array)
requestStuffTo(toBoolean, request)
Lots of functional languages can tell which version of a function to call by the type and you can have multiple definitions of the function, one for each type. So that can be even shorter:
var newArray = stuffTo(toBoolean, array)
var newRequest = stuffTo(toBoolean, request)
I can curry the arguments, then partially apply the function:
function stuffToBoolean = stuffTo(toBoolean)
var newArray = stuffToBoolean(array)
var newRequst = stuffToBoolean(request)
Now they are the same!
Now, when I want to add a new ThingWithStuff type, all I have to do is implement stuffTo for that thing.
function stuffTo(modifier, maybe) {
if (let Just thing = maybe) {
return Just(modifier(thing))
} else {
return Nothing
}
}
Now I can use the functions I already have with the new thing, for free!
var newMaybe = stuffToBoolean(maybe)
var newMaybe2 = stuffToString(maybe)
Or, I can add a new function:
function stuffTimesTwo(thing) {
return stuffTo((*)2), thing)
}
And I can already use it with any of the things!
var newArray = stuffTimesTwo(array)
var newResult = stuffTimesTwo(result)
var newMaybe = stuffTimesTwo(newMaybe)
I can even take an old function and easily turn it into one that works on any ThingWithStuff:
function liftToThing(oldFunction, thing) {
return stuffTo(oldFunction, thing)
}
function printThingContents = liftToThing(print)
(ThingWithStuff is usually called Functor, and stuffTo is generally called map)
You can do all the same things in an imperative language, but for example Haskell already has hundreds of different shape things and thousands of functions that work on those things. So if I add a new thing, all I have to do is tell Haskell what shapes it is and I can use those thousands of functions that already exist. Maybe I want to implement a new kind of Tree, I just say Tree is a Functor and I can use map to alter its contents. I just say it's an Applicative and with no more work I can put functions inside it and call it like a function. I say it's a Semiring and boom, I can add trees together. And all the other stuff out there that already works for Semirings just works on my Tree.