3

I'm an experienced software engineer, and have experience in a range of languages from PHP, Ruby, C#, Java - the last few years I've primarily worked in Java.

I'm now learning Scala, and it's my first real foray into functional programming.

I understand that my methods should not have side affects, and that I should favour immutability.

I am trying to build a small project which I've previously built in Java and Python, as an exercise. The application does the following:

  • Talk to a REST service to retrieve a set of configurations.
  • Maintain a copy/cache of those configurations, so they can be references in other areas of the software.
  • Listen for user input, and make calls to RESTful services again using the user's input as the command and the configurations we retrieved at startup.

It seems to me that since my class which deals with the RESTful API won't be referentially transparent (the output could differ for any number of reasons, it's an external API over which I have no control), I have to have some kind of state (the configurations), that maybe this software isn't as suited to a FP paradigm.

Am I thinking about this the wrong way?

Thankyou

4

Functional programming is not about not having state. It about keeping the functions referential transparency and without side effects. Usually this mean having no state, but that not has to be the case.
Referential transparency mean that for a given input parameters the function will always return the same output.
No having side effect mean that the function does not change the outer world, for example not changing some global parameter. This also mean that not calling the function wont effect other functions.

One example when there is a state but it still keep referential transparency and no side effects are caching.

class StatefullCache {
  val remoteRepository = ???
  var map: Map[String,Data] = Map()
  private def fetchFromRemote(id: String) = {
    val data = remoteRepository.get(id)
    map = map + (id -> data)
    data
  }
  def get(id: String) = map.getOrElse(id, fetchFromRemote(id))
}

This class have state but it referential transparency, since it always return the same result for any given id. It also does not has side effects (well... it has the memory growing, but that a limitation which does not effect the application flow).
In practice no application is a "pure functional". printing to the screen, file & network I/O have all size effects (and get to to the extreme, even the CPU heat is a side effect). The idea in functional programming is to keep the side effect to the minimum and isolated. Some languages force you do that. Scala does not force it, but encourage you do so.

Depending on your use case calling 3rd party API/REST/whatever which isn't immutable or has side effect, may be count for practical uses by your application as functional or not. For example if any running of the application is "new world" where the data may be change the class above will work well (Since it will only access the external service once per id).

To summarize it, if you minimize the mutability and the side effect and keep it all in control then you can have the benefits for functional programming in the rest of the app.

1

Consider the sentence

I understand that my methods should not have side affects, and that I should favour immutability.

Favour immutability means you should not use mutability when you don't need it, and that you should deal with it in a special way when you do need it.

There are many cases in which you can use mutation to compute something, whereas you can also use a mutation-free solution using recursion or high-order functions like map, fold, filter, and so on. For example, if you want to compute the sum of numbers from 1 to n, you can write

def sumMut(n: Int): Int = {
    var s = 0
    var i = 1
    while (i < n) {
        s = s + i
        i = i + 1
    }

    return s
}

using mutation, but you can also write

def sumImmut(n: Int): Int = {
    def loop(i: Int, acc: Int): Int = if (i < n) loop(i + 1, acc + i) else acc

    loop(1, 0)
}

without mutation. The former is an imperative / statefull solution, the latter is a functional / stateless solution.

In case where mutation is needed to store state (e.g. between intermediate calls to an object), the functional approach is to

  1. Keep the code that uses mutation to a minimum, and
  2. Clearly isolate that code from the rest of the code, which should be mutation-free.

Some functional languages offer explicit constructs to address point 2 (see e.g. monads in Haskell).

1

Functional programming is not about having no state. Instead, all that immutability stuff is about making state explicit. A simple example is adding a list of numbers. In Python, we would do something like this:

xs = [1, 2, 3, 4, 5]
sum = 0
for x in xs:
  sum += x

(Actually, we'd use the sum builtin, but this is just an example). This loop is stateful, because we mutate the sum variable at each iteration. The sum variable is not referentially transparent, because it points to different values at different points of time. The state is encoded in the control flow. Scala is not dogmatic about functional programming, and allows you to write code in a stateful way. However, you are encouraged to limit implicit state.

How could we make state explicit, or get rid of it when summing numbers? We can define the sum of a list as zero if the list is empty, and the first element plus the sum if the remaining elements otherwise. Note that this is a recursive definition. In Scala, we'd write it as

val xs = Seq(1, 2, 3, 4, 5)

def summer(xs: Seq[Int]): Int = xs match {
  case Seq() => 0
  case x :: rest => x + summer(rest)
}

val sum = summer(xs)

Note that while we have made the for … in … loop recursive, there still is no obvious state. This is because the recursive function uses the call stack to store intermediate values. The state of the loop becomes explicit if we write it as a tail-recursive function:

import scala.annotation.tailrec

@tailrec
def summer(xs: Seq[Int], acc: Int = 0): Int = xs match {
    case Seq() => acc
    case x :: rest => summer(rest, x + acc)
}

The acc (accumulator) argument represents the state here. It is not mutated, instead we create a new state for each iteration.

In fact, performing such an operation is so common that it's usually done via fold (or foldLeft, which allows the result to be of a different type):

def summer (xs: Seq[Int]): Int = (xs fold 0) (_ + _)

How does explicit state mesh with object-oriented programming? Quite well, actually: the object is the state. Think of the current object as a parameter that is invisibly being passed along in any method call. However, you never mutate the state, and instead create a new one. Careful though: this can make some data structures horribly inefficient if not designed with immutability in mind.

What does this mean for implementing a REST API? Nothing. It is your job as a programmer to decide which paradigms and patterns are suitable in any given situation. Using explicit state can have benefits, but it might also be obfuscation of what's really happening. In any case, different parts of the program can use different amounts of Functional Programming. Using FP patterns is possible even at a fairly small scale, and does not require your whole program to be written in absolute “purity”.

  • @Giorgio yes, Scala eliminates tail calls by default. However, as a programmer I sometimes want to make sure that this optimization was actually applied (e.g. to mitigate the risk of stack overflows). The tailrec annotation produces a compilation error if the compiler didn't recognize that TCE could be applied here; this usually indicates I made an error while coding. I think of @tailrec as analogous to type checking: It can be inferred, but sometimes I need to make sure the type is what I expect it to be. My Scala 2.10.3 REPL didn't seem to have it loaded, so I imported the annotation. – amon Oct 5 '14 at 10:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.