21

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?

BrianH
  • 6,092
  • 1
  • 21
  • 23
Ucenna
  • 1,292
  • 2
  • 10
  • 19
  • 32
    If you want your example to be taken seriously, have it solve a practical problem rather than a math problem. It's sort of a cliche among developers that "all FP is good for is solving math problems," and if your example is Yet Another Mathematical Function you're only reinforcing the stereotype, instead of making what you're doing look useful. – Mason Wheeler Sep 20 '16 at 19:23
  • Functional programming languages, especially pure ones like Haskell define a function as declaration of an expression value without state. The bottom line is, remembering a value in functional programming is inherently not functional because that implies state. When you encounter a problem that requires "state" or "consequences" like IO operations for example, then you can start looking at things like monads to bridge that gap between your functional and monadic code. – maple_shaft Sep 20 '16 at 19:32
  • 12
    Your attempt is actually pretty good when taking into account real world considerations. All of your recursive calls are [tail calls](https://en.wikipedia.org/wiki/Tail_call), that is, the function does nothing else after calling them. That means that a compiler or interpreter *that supports it* can optimize them so your recursive function uses a fixed amount of stack memory, rather than an amount proportional to `y`. – 8bittree Sep 20 '16 at 19:36
  • 1
    Thanks a lot for the support! I'm still very new at this, so my pseudocode isn't perfect. @MasonWheeler I guess, in this case my code isn't really meant to be taken seriously. I'm still learning, and the reason I love FP is because it is Math-y. The whole point of my example is to explain to my teacher why I'm using FP. He doesn't really understand what it is, so this seemed like a good way to show him the advantages. – Ucenna Sep 20 '16 at 19:40
  • https://medium.com/@chetcorcos/functional-programming-for-javascript-people-1915d8775504#.2poxojh4h – OrangeDog Sep 21 '16 at 08:32
  • 5
    In which language do you plan to write the code? Don't try to use a style that is not suitable for the language that you are using. – Carsten S Sep 21 '16 at 08:35
  • As @CarstenS suggests, choosing the language will be key to getting help. the Language must have a look and feel that is 'close' to your instructors world experience. Ask the instructor is they have heard of any FP languages in their (or the course's) domain and work on that language. It doesn't help that Humans are normally work with a narrative/imperative based world view. Those in maths, science and engineering often (self selected) have a functional view - choose your audience! – Philip Oakley Sep 21 '16 at 13:08
  • 2
    Possibly useful: https://en.wikipedia.org/wiki/Memoization – Ethan Bolker Sep 21 '16 at 13:16
  • @CarstenS On my own I'm learning Haskell, however for the purpose of class I'll be learning JavaScript. At the moment I'm not terribly familiar with the syntax of either, hence the pseudocode. Any functional programming will have to be self enforced. ;) – Ucenna Sep 21 '16 at 15:12
  • Is this "web development" course client side or server side? Also, be careful. *There are no silver bullets.* Even functional programming. It has benefits and downsides, and those need to be weighed against your requirements. I had to learn the hard way that there are *very* few things in programming that you can just blindly do and your work will automatically be better for it. – jpmc26 Sep 21 '16 at 15:28
  • @jpmc26 I assume your referring to whether or not the code is run on the hose computer or the websites server. It's mixed, with a slight focus on server side. – Ucenna Sep 21 '16 at 15:30
  • If you don't know where you can bounce question off people. If you are learning Haskell as functional programming language, you can head over to #Haskell on freenode. ;-) – Dylan Meeus Sep 21 '16 at 15:50
  • Half the point of functional programming is that you don't remember values. – user253751 Sep 21 '16 at 22:10

4 Answers4

38

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.

gardenhead
  • 4,719
  • 2
  • 19
  • 24
  • Your recursive `pow` isn't quite right. As it is, `pow 3 3` returns `81`, instead of `27`. It should be `else x * pow x (y-1).` – 8bittree Sep 20 '16 at 19:02
  • What 8bittree said (and is that Haskell? I'm learning it now, and it looks similar). Also, since for the purpose of my course I'll be using Javascript primarily, what would a function like that look like in javascript? – Ucenna Sep 20 '16 at 19:08
  • 3
    Whoops, 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 Sep 20 '16 at 19:11
  • :P @gardenhead Thanks a lot! That makes a lot of sense, I had never thought of multiply x by the function. Guess I still have a long way to go! ;) – Ucenna Sep 20 '16 at 19:13
  • "either your imperative or functional code" - that is probably more advanced topic but the OP code looks tail recursive so if compiler implements tail call optimization or tail recursion optimization it may produce 'better' code. That said this is probably topic for later. – Maciej Piechotka Sep 21 '16 at 01:51
  • (FWIW, your SML code is almost correct: the type is called `real`, and there's no trailing period (in the repl, use a semicolon).) – wchargin Sep 21 '16 at 04:02
  • Javascript is a functional language? – jwg Sep 21 '16 at 08:35
  • 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 Sep 21 '16 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 Sep 21 '16 at 14:09
  • To "consider the loop above to be syntactic sugar for iterative recursive processes.", you'd have to be able to recognize that the multiplication in `x * (pow x (y-1))` could be converted to an accumulator-based implementation. Otherwise, you need to finish the recursive call to pow, get the result, *then* multiply by x, and then return that. OP's original version with an accumulator was closer to syntactic sugar for an iterative version, in that regard. – Joshua Taylor Sep 21 '16 at 15:15
  • -1 for the editorializing about which paradigms and tools are better/worse. That's subjective and doesn't add to answering the OP's question. – Paul Sep 21 '16 at 15:43
  • @Paul the only editorializing I did was stating that JavaScript is painful, which I'm most developers (even JS developers) would agree with. I was just trying to help the OP beyond answering the stated question by encouraging him to continue to learn FP but also to learn what he can from the class he's currently taking. – gardenhead Sep 21 '16 at 15:45
  • @gardenhead Actually if you did a survey you'd almost certainly find that much MUCH more people find Haskell painful. Which just goes to show that popularity is a pretty bad metric or argument. Anyhow the problem with trying to do functional programming in a non-functional language is that it's just not made for it and you'll notice that in practice. Are those two functions really "more or less identical"? Mathematically speaking yes, but in practice the recursive one is going to run much slower and fail for large input values (no TCO in any JS engine that I know of). – Voo Sep 21 '16 at 16:02
  • 1
    Popularity 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 Sep 21 '16 at 16:08
  • This pow uses memory linear in y, which is pretty bad – VF1 Sep 23 '16 at 04:00
  • @VF1 I am aware. I know how to turn it into an iterative process, but this question is not about performance, and IMO the less efficient version of the code is easier to understand. – gardenhead Sep 23 '16 at 14:11
  • Absolutely, it's definitely more clear, but this seems like you're hiding the fact that functional programs have to use accumulator variables. I think your answer would be (1) a more direct translation of the procedural one and (2) representative of the pros and cons of FP if it included the additional param in a local function. – VF1 Sep 23 '16 at 14:16
  • When you say `z` isn't necessary, the code snippet below still provides an extra variable, `ret`. – Panzercrisis Aug 18 '17 at 20:25
34

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.

Jack
  • 4,449
  • 2
  • 26
  • 30
  • I think you got your if clauses backwards in `fold_recursive`. It should `return initial_state` when empty, and do the work when not empty. – 8bittree Sep 20 '16 at 21:02
  • 8
    Definitely 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 Sep 20 '16 at 22:23
  • 1
    In some languages, you can just write `fold(repeat(base, power), 1, *)` – user253751 Sep 20 '16 at 23:07
  • immibis: And in others, `exponent = foldl * 1 ... replicate` ;) – Jack Sep 21 '16 at 01:31
  • 1
    I've also heard the operation `scan` in Rx. Any big difference between `fold`, `reduce`, and `scan`? – Rico Kahler Sep 21 '16 at 04:18
  • 4
    Rico Kahler: `scan` is essentially a `fold` 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 of `fold` (every looping operation is). – Jack Sep 21 '16 at 07: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 Sep 21 '16 at 13:20
  • Thanks! Sorry if I'm being stupid, but what is a combiner_func()? At first it looks like a variable, but then it's a function, and then I'm confused. Just goes to show how much I still need to learn... – Ucenna Sep 21 '16 at 13:53
  • 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, and `sum_all` is passing an *anonymous function* (that's the `lambda` bit - it creates a function value without naming it) that defines how it wants to combine two items together. – Jack Sep 21 '16 at 15:53
  • @Ucenna Remember, in FP, you'll often be passing functions to other functions. A fold will always accept a function that takes 2 arguments. 1 argument is the running accumulator. The other argument is the current item of the list. If you were summing a list of numbers using a fold, the accumulator would be the sum so far that you're adding to, while the other argument would be the current number that you want to add to the sum. If you understand how map works, it's similar; only a map operation doesn't keep a running accumulator, so it's function only takes 1 argument. – Carcigenicate Sep 21 '16 at 15:54
  • @Ucenna So, `combiner_func` is a function, **and** a variable. Look up Higher Order Functions to get a good intro into this. It may seem overly complicated, but trust me: once you realize the power of being able to pass functions around as variables, you'll wonder how you ever programmed without that ability. Understand how map functions work first, then deal with folds. Once you understand a map, folds will be much easier. – Carcigenicate Sep 21 '16 at 15:56
  • @Ucenna I added a lengthy answer below that attempts to give a introduction into each concept. – Carcigenicate Sep 21 '16 at 16:18
  • `fold` and `reduce` are generally the same thing. Other names are `accumulate` (C++) and `aggregate` (.NET). Scala has both general `fold` of type `(b → a → b) → b → [a] → b` and the more restrictive `reduce` with type `(a → a → a) → [a] → a`, which doesn't take an accumulator argument and thus can only be applied to non-empty lists (called `fold1` in Haskell, for "fold with at least one element" or "fold with the first element as the accumulator"). `scan` is a different operation, also known as `PREFIX-SUM`. – Jörg W Mittag Sep 22 '16 at 22:07
  • Python has reduce already, by the way. `from functools import reduce`. – Miles Rout Sep 28 '16 at 04:28
8

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:

  1. 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.

  2. 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.

  3. 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.

Carcigenicate
  • 2,634
  • 3
  • 24
  • 38
  • Gotcha. So if I understand correctly `fold(x,y,z)` is basically just a function that does `fold(x,y,z[0]){ if z[i] < "length of z"; then fold(x,x(y,z),z[i+1]); else return y;)` in very, very, simple terms. (Sorry, that looks a bit messy, just wanted to make sure I had the right idea.) – Ucenna Sep 21 '16 at 17:03
  • 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 time `x` 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 (whatever `x` returned the first time), and the *second* element of the list as it's second argument. – Carcigenicate Sep 21 '16 at 17:10
  • 1
    @Ucenna Now that you have the basic idea, look at Jack's implementation again. – Carcigenicate Sep 21 '16 at 17:12
  • Yup, that did it for me. It looks like his flips the functions parameters. Otherwise though, I've got the concept down. I'm just not used to using functions as values. Thanks! – Ucenna Sep 21 '16 at 17:21
  • 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 Sep 21 '16 at 17:27
  • 1
    @Ucenna Ya, the order I'm using is for Haskell and Clojure. Since the items are last, you can "cut them off" to create a new function. For example (in Haskell): `add-two-to-each = map (+ 2)`. I now have a function (`add-two-to-each`) that takes a list, and adds two to each element; defined in terms of a map. Partial application can be handy, and the order of arguments defines how well functions compose together. – Carcigenicate Sep 21 '16 at 17:35
  • 1
    @Ucenna Whoops, you're referring to the argument order of the passed function. Sorry. That differs depending on if you're doing a left fold, or a right fold. A left fold has the accumulator on the left, and consumes the left of the list first. The right fold has the acc on the right, and consumes the list from the right side first (or at least that's how Haskell differs). Think about why this matters if the function you pass is a `-` or `/` function. – Carcigenicate Sep 21 '16 at 17:39
  • Oops! My bad, got my words mixed up. I did mean arguments. – Ucenna Sep 21 '16 at 17:42
  • 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 as `func map(f, l) = fold((xs, x) => append(xs, f(x)), [], l)` Here, the "single value" computed by `fold` is actually a list. – Jörg W Mittag Sep 22 '16 at 21:49
  • @JörgWMittag Yes. The accumulator that the fold uses and results in can be any object, which could hold any number of any other objects. I considered adding that, but didn't want to confuse things. Maybe I'll try to slip in some simple example to clarify that. Some sample code that defines a map in terms of a fold as you suggest may actually be a good idea, since it would tie the two concepts together. Thanks. – Carcigenicate Sep 22 '16 at 21:53
  • 1
    In the Ruby community, I often encounter people who are surprised about that. They think that "fold is just for adding numbers" or something like that. There's a nice talk by Rúnar Bjarnason where he uses folds to introduce FP, and he gives some nice examples of the power of folds and their generality. The idea is basically: a list is like an instruction stream with two possible instructions, `EMPTY` and `ELEMENT(x)`, and `fold` is a programmable interpreter for that instruction stream that allows you to pass implementations for interpreting both instructions. Therefore, everything you might … – Jörg W Mittag Sep 22 '16 at 21:58
  • 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 Sep 22 '16 at 22:00
  • @JörgWMittag I posted an example. My pseudocode started to become this gross mix of JS and Python though :/. And if I recall, Haskell has a Monad that defines what you're describing. "Foldable" I believe it's called. I think FP has at least 2 click moments: 1 when you fully understand what a pure function is, and why it's an important concept, and 2 when you realize that everything except for complicated parsing really can fairly easily be expressed as a fold. – Carcigenicate Sep 22 '16 at 22:09
  • 1
    I agree. "An interpreter is just a left fold over a parse tree" is probably one of my favorite quotes, along with the infamous "monads are just monoids in the category of endofunctors". And a really obscure one, when Simon Peyton-Jones gave a class on Haskell at OSCON 2008, he said something like "programs without side-effects are boring, all they do is make the CPU hot", to which someone from the audience yelled "that *is* a side-effect!" – Jörg W Mittag Sep 22 '16 at 22:13
  • @JörgWMittag Honestly, I hate parsing using folds. My accumulators usually end up as a messy state of what's been processes, and usually end up resorting to explicit recursion if it gets bad enough. And ROFL. Technically ya. There are always side effects! They should just be limited to what's necessary (or unavoidable). – Carcigenicate Sep 22 '16 at 22:17
  • I think it's important to note that, at least with most languages that are intended to be used with a functional programming style, *fold works on iterators rather than just lists* (Haskell may confuse the issue, but note that due to lazy evaluation a Haskell list is actually effectively the same thing as an iterator...). This means that if you want to work on a sequence of numbers, you *don't have to have that sequence in memory*, you can just use an iterator object that produces them one by one (like Python's "range" or .NET's Enumarable.Range). – Periata Breatta Sep 23 '16 at 04:17
1

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.

  • 1
    In order to logically link this answer to the others, it should be noted that `product` is just a shortcut function to `fold` with multiplication as its function and 1 as its initial argument, and that `replicate` 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 Sep 23 '16 at 04:24