I've decided to take upon myself the task of learning functional programming. So far it's been a blast, and I've 'seen the light' as it were. Unfortunately, I don't actually know any functional programmer that I can bounce questions off of. Introducing Stack Exchange.
I'm taking a web/software development course, but my instructor isn't familiar with functional programming. He's fine with me using it, and he just asked me to help him understand how it works so he can read my code better.
I decided the best way to do this would be by illustrating a simple mathematical function, like raising a value to a power. In theory I could easily do that with a prebuilt function, but that would defeat the purpose of an example.
Anyway, I'm having some difficulty figuring out how to hold a value. Since this is functional programming I can't change variable. If I were to code this imperatively, it would look something like this:
(The following is all pseudocode)
f(x,y) {
int z = x;
for(int i = 0, i < y; i++){
x = x * z;
}
return x;
}
In functional programming, I wasn't sure. This is what I came up with:
f(x,y,z){
if z == 'null',
f(x,y,x);
else if y > 1,
f(x*z,y-1,z);
else
return x;
}
Is this right? I need to hold a value, z
in both cases, but I wasn't sure how to do this in function programming. In theory, the way I did it works, but I wasn't sure if it was 'right'. Is there a better way to do it?
4 Answers 4
First of all, congratulations on "seeing the light". You've made the software world a better place by expanding your horizons.
Second, there is honestly no way a professor who doesn't understand functional programming is going to be able to say anything useful about your code, other than trite comments such as "the indentation looks off". This isn't that surprising in a web development course, as most web development is done using HTML/CSS/JavaScript. Depending on how much you actually care about learning web development, you might want to put in the effort to learn the tools your professor is teaching (painful though it may be - I know from experience).
To address the stated question: if your imperative code uses a loop, then chances are your functional code is going to be recursive.
(* raises x to the power of y *)
fun pow (x: real) (y: int) : real =
if y = 1 then x else x * (pow x (y-1))
Note that this algorithm is actually more or less identical to the imperative code. In fact, one could consider the loop above to be syntactic sugar for iterative recursive processes.
As a side note, there's no need for a value of z
in either your imperative or functional code, in fact. You should have written your imperative function like so:
def pow(x, y):
var ret = 1
for (i = 0; i < y; i++)
ret = ret * x
return ret
rather than changing the meaning of the variable x
.
-
Your recursive
pow
isn't quite right. As it is,pow 3 3
returns81
, instead of27
. It should beelse x * pow x (y-1).
8bittree– 8bittree2016年09月20日 19:02:20 +00:00Commented Sep 20, 2016 at 19:02 -
3Whoops, writing correct code is hard :) Fixed, and I also added type annotations. @Ucenna It's supposed to be SML, but I haven't used it in a while so I might have the syntax slightly wrong. There's too many darn ways to declare a function, I can never remember the right keyword. Besides syntax changes, the code is identical in JavaScript.gardenhead– gardenhead2016年09月20日 19:11:21 +00:00Commented Sep 20, 2016 at 19:11
-
2@jwg Javascript does have some functional aspects: functions can define nested functions, return functions, and accept functions as parameters; it supports closures with lexical scope (no lisp dynamic scope though). It's up to the programmer's discipline to refrain from changing state and mutating data.Kasper van den Berg– Kasper van den Berg2016年09月21日 12:04:09 +00:00Commented Sep 21, 2016 at 12:04
-
1@jwg There is no agreed-upon definition of "functional" language (nor for "imperative", "object-oriented", or "declarative"). I try to refrain from using these terms whenever possible. There are too many languages under the sun to be categorized into four neat groups.gardenhead– gardenhead2016年09月21日 14:09:42 +00:00Commented Sep 21, 2016 at 14:09
-
1Popularity is an awful metric, which is why whenever someone mentions that language or tool X must be better because it's so widely used I know continuing the argument would be pointless. I'm more familiar with the ML family of languages than Haskell personally. But I'm also not sure if it's true; my guess would be the vast majority of developers haven't tried Haskell in the first place.gardenhead– gardenhead2016年09月21日 16:08:13 +00:00Commented Sep 21, 2016 at 16:08
This is really just an addendum to gardenhead's answer, but I'd like to point out there's a name for the pattern you're seeing: folding.
In functional programming, a fold is a way to combine a series of values that "remembers" a value between each operation. Consider adding a list of numbers imperatively:
def sum_all(xs):
total = 0
for x in xs:
total = total + x
return total
We take a list of values xs
and an initial state of 0
(represented by total
in this case). Then, for each x
in xs
, we combine that value with the current state according to some combining operation (in this case addition), and use the result as the new state. In essence, sum_all([1, 2, 3])
is equivalent to (3 + (2 + (1 + 0)))
. This pattern can be extracted into a higher order function, a function that accepts functions as arguments:
def fold(items, initial_state, combiner_func):
state = initial_state
for item in items:
state = combiner_func(item, state)
return state
def sum_all(xs):
return fold(xs, 0, lambda x y: x + y)
This implementation of fold
is still imperative, but it can be done recursively as well:
def fold_recursive(items, initial_state, combiner_func):
if not is_empty(items):
state = combiner_func(initial_state, first_item(items))
return fold_recursive(rest_items(items), state, combiner_func)
else:
return initial_state
Expressed in terms of a fold, your function is simply:
def exponent(base, power):
return fold(repeat(base, power), 1, lambda x y: x * y))
...where repeat(x, n)
returns a list of n
copies of x
.
Many languages, particularly those geared towards functional programming, offer folding in their standard library. Even Javascript provides it under the name reduce
. In general, if you find yourself using recursion to "remember" a value across a loop of some sort, you probably want a fold.
-
8Definitely learn to spot when a problem can be solved by a fold or a map. In FP, nearly all loops can be expressed as fold or a map; so explicit recursion usually isn't necessary.Carcigenicate– Carcigenicate2016年09月20日 22:23:12 +00:00Commented Sep 20, 2016 at 22:23
-
1In some languages, you can just write
fold(repeat(base, power), 1, *)
Stack Exchange Broke The Law– Stack Exchange Broke The Law2016年09月20日 23:07:45 +00:00Commented Sep 20, 2016 at 23:07 -
4Rico Kahler:
scan
is essentially afold
where instead of just combining the list of values into one value, it is combined and each intermediate value is spit back out along the way, producing a list of all the intermediate states the fold created instead of just producing the final state. It's implementable in terms offold
(every looping operation is).Jack– Jack2016年09月21日 07:01:30 +00:00Commented Sep 21, 2016 at 7:01 -
4@RicoKahler And, as far as I can tell, reductions and folds are the same thing. Haskell uses the term "fold", while Clojure prefers "reduce". Their behaviour seem the same to me.Carcigenicate– Carcigenicate2016年09月21日 13:20:33 +00:00Commented Sep 21, 2016 at 13:20
-
2@Ucenna: It is both a variable and a function. In functional programming, functions are values just like numbers and strings - you can store them in variables, pass them as arguments to other functions, return them from functions, and generally treat them like other values. So
combiner_func
is an argument, andsum_all
is passing an anonymous function (that's thelambda
bit - it creates a function value without naming it) that defines how it wants to combine two items together.Jack– Jack2016年09月21日 15:53:23 +00:00Commented Sep 21, 2016 at 15:53
This is a supplemental answer to help explain maps and folds. For the examples below, I'll use this list. Remember, this list is immutable, so it will never change:
var numbers = [1, 2, 3, 4, 5]
I'll be using numbers in my examples because they lead to easy to read code. Remember though, folds can be used for anything a traditional imperative loop can be used for.
A map takes a list of something, and a function, and returns a list that was modified using the function. Each item is passed to the function, and becomes whatever the function returns.
The easiest example of this is just adding a number to each number in a list. I'll use pseudocode to make it language agnostic:
function add-two(n):
return n + 2
var numbers2 =
map(add-two, numbers)
If you printed numbers2
, you would see [3, 4, 5, 6, 7]
which is the first list with 2 added to each element. Notice the function add-two
was given to map
to use.
Folds are similar, except the function you're required to give them must take 2 arguments. The first argument is usually the accumulator (in a left fold, which are the most common). The accumulator is the data that's passed while looping. The second argument is the current item of the list; just like above for the map
function.
function add-together(n1, n2):
return n1 + n2
var sum =
fold(add-together, 0, numbers)
If you printed sum
you would see the sum of the list of numbers: 15.
Here are what the arguments to fold
do:
This is the function that we're giving the fold. The fold will pass the function the current accumulator, and the current item of the list. Whatever the function returns will become the new accumulator, which will be passed to the function the next time. This is how you "remember" values when you're looping FP-style. I gave it a function that takes 2 numbers and adds them.
This is the initial accumulator; what the accumulator starts as before any items in the list are processed. When you're summing numbers, what's the total before you've added any numbers together? 0, which I passed as the second argument.
Lastly, as with the map, we also pass in the list of numbers for it to process.
If folds still don't make sense, consider this. When you write:
# Notice I passed the plus operator directly this time,
# instead of wrapping it in another function.
fold(+, 0, numbers)
You're basically putting the passed function between each item in the list, and adding the initial accumulator onto either the left or right (depending on if it's a left or right fold), so:
[1, 2, 3, 4, 5]
Becomes:
0 + 1 + 2 + 3 + 4 + 5
^ Note the initial accumulator being added onto the left (for a left fold).
Which equals 15.
Use a map
when you want to turn one list into another list, of the same length.
Use a fold
when you want to turn a list into a single value, like summing a list of numbers.
As @Jorg pointed out in the comments though, the "single value" need not be something simple like a number; it could be any single object, including a list or a tuple! The way I actually had folds click for me was to define a map in terms of a fold. Note how the accumulator is a list:
function map(f, list):
fold(
function(xs, x): # xs is the list that has been processed so far
xs.add( f(x) ) # Add returns the list instead of mutating it
, [] # Before any of the list has been processed, we have an empty list
, list)
Honestly, once you understand each, you'll realize almost any looping can be replaced by a fold or a map.
-
1@Ucenna @Ucenna There's a couple flaws with your code (like
i
never being defined), but I think you have the right idea. One problem with your example is: the function (x
), is passed only one element of the list at a time, not the entire list. The first timex
is called, it's passed your initial accumulator (y
) as it's first argument, and the first element as it's second argument. The next time it's run,x
will be passed the new accumulator on the left (whateverx
returned the first time), and the second element of the list as it's second argument.Carcigenicate– Carcigenicate2016年09月21日 17:10:20 +00:00Commented Sep 21, 2016 at 17:10 -
1@Ucenna Now that you have the basic idea, look at Jack's implementation again.Carcigenicate– Carcigenicate2016年09月21日 17:12:12 +00:00Commented Sep 21, 2016 at 17:12
-
1@Ucenna: Different langs have different preferences for whether the function given to fold takes the accumulator as the first or second argument, unfortunately. One of the reasons it's nice to use a commutative operation like addition to teach folds.Jack– Jack2016年09月21日 17:27:36 +00:00Commented Sep 21, 2016 at 17:27
-
3"Use a
fold
when you want to turn a list into a single value (like summing a list of numbers)." – I just want to note that this "single value" can be arbitrarily complex ... including a list! Actually,fold
is a general method of iteration, it can do everything iteration can do. E.g.map
can be trivially expressed asfunc map(f, l) = fold((xs, x) => append(xs, f(x)), [], l)
Here, the "single value" computed byfold
is actually a list.Jörg W Mittag– Jörg W Mittag2016年09月22日 21:49:18 +00:00Commented Sep 22, 2016 at 21:49 -
2... possibly want to do with a list, can be done with
fold
. And it doesn't have to be a list, every collection that can be expressed as empty/not empty will do. Which basically means that any iterator will do. (i guess throwing the word "catamorphism" in there would be too much for a beginner's introduction, though :-D )Jörg W Mittag– Jörg W Mittag2016年09月22日 22:00:17 +00:00Commented Sep 22, 2016 at 22:00
It's hard to find good problems that can't be solved with build in functionality. And if it is built in, then it should be used to be an example of good style in language x.
In haskell for example you already have the function (^)
in Prelude.
Or if you want to do it more programmaticaly product (replicate y x)
What I'm saying is that it is hard to show the strengths of a style/language if you don't use the features it provides. However it might be a good step towards showing how it works behind the scenes, but i think you should code the best way in whatever language you are using and then help the person from there to understand what is going on if needed.
-
1In order to logically link this answer to the others, it should be noted that
product
is just a shortcut function tofold
with multiplication as its function and 1 as its initial argument, and thatreplicate
is a function that produces an iterator (or list; as I noted above the two are essentially indistinguishable in haskell) that gives a given number of identical outputs. It should be easy to understand now how this implementation does the same thing as @Jack's answer above, just using predefined special-case versions of the same functions to make it more succinct.Periata Breatta– Periata Breatta2016年09月23日 04:24:32 +00:00Commented Sep 23, 2016 at 4:24
y
.