As a newbie in functional languages (I started touching Erlang a couple of weeks ago -- the first functional language I could get my hands on).
I started to writing some
After a while, I found that functional programming [...] encourages a "top down" design.
Well, it's not about "top down" or "bottom up" design really. It's about focusing on the "what" of the problem at hand, rather than the "how". When I started off with functional programming, I found that I kept recalling imperative constructs like the nested for
loop in C. Then I quickly found out that trying to translate my imperative thinking to functional constructs was very difficult. I'll try to give you a more concrete example. I'll implement an equivalent program in C and Haskell and attempt to trace my thought process in both cases. Note that I've been explicitly verbose for the purpose of explanation.
In C:
#include <stdio.h>
int main(void)
{
int i, inputNumber, primeFlag = 1;
scanf("%d", &inputNumber);
for(i = 2; i <= inputNumber / 2; i ++)
{
if (inputNumber % i == 0)
{
primeFlag = 0;
break;
}
}
if (primeFlag == 0) printf("False\n");
else printf ("True\n");
return 0;
}
Trace of my thought process:
inputNumber
. scanf() written.primeFlag
declared and set equal to 1
.primeNumber
against every number from 2 to primeNumber/2
. for
loop started. Declared a loop variable i
to check primeNumber
against.primeNumber
against each i
. The moment we find even one i
that divides primeNumber
, set primeFlag
to 0
and break
. Loop body written.for
loop, check the value of primeFlag
and report it to the user. printf() written.In Haskell:
assertPrime :: (Integral a) => a -> Bool
assertPrime x = null divisors
where divisors = takeWhile (<= div x 2) [y | y <- [2..], mod x y == 0]
Trace of my thought process:
null divisors
.divisors
? First, let's write down a list of possible candidates. Wrote down Texas range from 2 to number/2.mod x y == 0
I want to get advice about how to "think in functional language"
Ok, first and foremost, think "what", not "how". This can take a lot of practice to get used to. Also, if you were formerly a C/C++ programmer like me, stop worrying about memory! Modern languages have a garbage collector, and it's written for you to use- so don't even try to modify variables in place. Another thing that has personally helped me: write down English-like definitions in your program to abstract out the functions that do the heavy-lifting.
It’s import to get used to thinking that data can be used as code, and vice versa.
Usually you construct a program (data) using several primitive operations (folding, nesting, threading, distributing, ..., and some are generalized inner product, outer product, etc.), and use this program (data) to manipulate other data.
After a while, I found that functional programming […] encourages a "top down" design.
I agree.
I found myself often getting lost in decisions such as "should I use a helper List for intermediate result storage?" and "should I create a helper function to do this?"
My advice for this: read The Little Schemer. You can follow it in Erlang. It's a good book to drill a sense of this into you.
An answer is that functional programming is to program using functions, as they are defined in mathematics (in short, side-effect free things that map values from the domain to the codomain). To actually translate that into "how to think" is the hand-waving part that is difficult to be exhaustive about, but I'll sample some of my thoughts:
Number one is associated with the vague: "elegant code". List comprehensions can present very succinct and mathematical-equations like definitions of functions. Just look at the quick-sort implemented with LCs. This is how I define elegance, succinct and makes all behaviours clear. Not that perl code-golf where you are most often terse and cryptic.
Number two is something that I use day to day in all programming. Divide code into functions (methods, routines, etc...) of current state that are side-effect free computations giving inputs to the next action to take (even which the next action to take is). When the value is returned, give it to a routine that performs the action that is described, then start over.
In my head I diagram an Erlang process as a state machine graph, where each vertex is a side-effect and a function whose output is which edge to chose out of the vertex. The high regard of side-effects is something the functional programming paradigm taught me. Especially in Erlang, since side-effects really matter in concurrency, and Erlang makes concurrency very available.
The same way some isolated tribes have only one word for numbers above 3, or no words for "mine"/"yours". It feels like popular languages do not have words for "this will cause a side-effect", but Functional Programming has it. It is forcing you to be aware of it all the time, and that is a good thing.
After a while, I found that functional programming […] encourages a "top down" design.
I'm not sure this is an accurate statement. I've been recently trying to teach myself functional programming, and I've found that a sort "bottom-up" style of programming really helps me. To use your example of merge sort:
:)
I could be misusing the term, but this feels like bottom-up design to me. Functional programming is different than object-oriented programming, but you shouldn't need to totally abandon existing design techniques when switched between the two.