Scala: What do “effect” and “effectful” mean in functional programming?

When you get started with functional programming (FP) a common question you’ll have is, “What is an effect in functional programming?” You’ll hear advanced FPers use the words effects and effectful, but it can be hard to find a definition of what these terms mean.

Back to top

Effects are related to monads

A first step in the process of understanding effects is to say that they’re related to monads, so you have to know a wee bit about monads to understand effects.

As I wrote in my book, Functional Programming, Simplified, a slight simplification is to say that in Scala, a monad is any class that implements the map and flatMap methods. Because of the way Scala for-expressions work, implementing those two methods lets instances of that class be chained together in for-expressions (for/yield expressions).

The benefit of monads

That leads to the benefit of monads in Scala: they let you sequence a series of operations together. For example, if every function returns an Option, you can sequence the functions together in a for-expression:

def fInt(): Some[Int] = Some(1)
def fDouble(): Some[Double] = Some(1.0)
def fString(): Some[String] = Some("alvin")

val x = for {
    a <- fInt()
    b <- fDouble()
    c <- fString()
} yield (a,b,c)
// x: Option[(Int, Double, String)] = Some((1,1.0,alvin))

Indeed, when people use the term monadic in Scala you can typically replace the word “monadic” with “in a for-expression.”

Summary: Step 1 is knowing that a monad in Scala is simply a class that implements map and flatMap so it can be used in a for-expression. (That’s a slight over-simplification, but good enough for now.)

Back to top

Not a side effect, but the main effect

Now that we know that we’re talking about monads, the next important part is to understand the meaning of the word “effect.” A good way to describe this is to say that we’re not talking about a side effect — instead we’re talking about a main effect, i.e, the main purpose of each individual monad. An effect is what the monad handles.

This first became clear when I read the book, Functional and Reactive Domain Modeling, and came across these statements:

  • Option models the effect of optionality
  • Future models latency as an effect
  • Try abstracts the effect of failures (manages exceptions as effects)

Those statements can also be written like this:

  • Option is a monad that models the effect of optionality (of something being optional)
  • Future is a monad that models latency as an effect
  • Try is a monad that models the effect of failures (manages exceptions as effects)

Similarly:

  • Reader is a monad that models the effect of composing operations that depend on some input
  • Writer is a monad that models logging as an effect
  • State is a monad that models the effect of state (composing a series of computations that maintain state)

So again, an effect is the thing a monad handles. In terms of code, it’s how a monad implements its flatMap method to achieve that effect.

Note: In Haskell, the equivalent of the flatMap method is known as bind.

Back to top

Effectful functions return F[A] rather than [A]

In a YouTube video titled, Functional Programming with Effects, Rob Norris makes an interesting point: he says that an effectful function is a function that returns F[A] rather than [A]. For example, this function returns Option[Int] rather than Int:

def toInt(s: String): Option[Int] = {
    try {
        Some(Integer.parseInt(s.trim))
    } catch {
        case e: Exception => None
    }
}

In creating toInt you could write a function that returns Int, but the only ways to do that are:

  • Return 0 if the conversion fails, or
  • Return null if the conversion fails

Regarding the first case, this is a bad idea because users will never know if the function received "0" or something like "fred" that won’t convert to a number. Regarding the second case, using null is a bad practice in both OOP and FP, so that approach is just a bad idea.

Therefore, it occurs to you that a logical thing to do is to return an Option from toInt. Option lets you handle the optional possible return values from toInt:

  • Some[Int] if toInt succeeds
  • None if the toInt conversion fails

 

This is what it means when we say, “Option is a monad that models the effect of optionality,” and it’s also what Mr. Norris means when he says that an effectful function returns F[A] rather than [A]. In the toInt example:

  • F[A] is Option[Int]
  • A is the raw Int type

Now, because toInt is effectful — meaning that it returns an F[A], which is a monadic type — it can be used in for-expressions like this:

val x = for {
    a <- toInt(string1)
    b <- toInt(string2)
    c <- toInt(string3)
} yield (a + b + c)

The result of this expression is that x will be a Some[Int] or a None.

I don’t remember where I read it, but someone said that if you want to think about it philosophically, when a function returns an A, that A has already been fully evaluated; but if that function returns F[A] instead, that result has not already been fully evaluated, the A is still inside F[A] waiting to be evaluated. So, rather than writing a function that returns a raw type, an effectful function returns a raw type inside a useful wrapper — where that wrapper is a monad that lets the result be used in a monadic style (i.e., in a Scala for-expression).

Back to top

Summary

For some reason my brain has a hard time absorbing the words effect and effectful when people talk about things abstractly, so I decided to dig into this topic and then share my notes here. I hope you find them helpful as well.

As mentioned, rather than thinking of a side effect, an effect is the main effect of a monad that you’re using:

  • Option is a monad that models the effect of optionality
  • Future is a monad that models latency as an effect
  • Try is a monad that models the effect of failures (manages exceptions as effects)
  • Reader is a monad that models the effect of composing operations that depend on some input
  • Writer is a monad that models logging as an effect
  • State is a monad that models the effect of state (composing a series of computations that maintain state)

Furthermore, when a function is said to be effectful, it simply means that the function is returning a monad, i.e., some type F[A] rather than the raw type A.

Back to top

Notes

In my programming life I need to move on to other topics, so I wrote this post quickly. It would be more effective if I showed you how to write flatMap and map functions in a monad, but I already did that in Functional Programming, Simplified, so I won’t repeat that here.

A few other notes:

  • I oversimplified the definition of monad in that discussion. There are formal rules about monads that are important, and I discuss those in Functional Programming, Simplified. But a useful simplification is that any class that implements map is a functor, and any class that further implements flatMap (in addition to map) is a monad.
  • In the preceding discussion I used Option for my examples, but you can also use instances of Try or Either, if you prefer.
  • I could have written toInt shorter (as shown below) but I wanted to clearly show the Option/Some/None types in the function body:
def toInt(s: String): Option[Int] = Try(Integer.parseInt(s.trim)).toOption

Finally, in a shameless plug, if you’re interested in functional programming but parts of that conversation didn’t make sense, I encourage you to read my book, Functional Programming, Simplified:

Back to top

Comments

Permalink

The last time i had the feel to really understand a paradigma was when i read "Elegant Objects" by Yegor Bogayenko. Reading your book and this blog i have similar feelings now in the context of functional programming again.

Permalink

After reading this excellent post, I was compelled to buy a copy of pf-simplified. You have a real gift for explaining things, something we need much more of in the Scala world. I have been using Scala for over 10 years, and I am still struggling with grokking it.