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.
Effects are related to monads (but don’t worry)
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 (i.e., for
/yield
expressions).
(If you thought understanding monadswas hard, I hope that helps to make them easier to understand.)
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
andflatMap
so it can be used in a for-expression. (That’s a slight over-simplification, but it’s good enough for now.)
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 optionalityFuture
models latency as an effectTry
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 effectTry
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 inputWriter
is a monad that models logging as an effectState
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 asbind
.
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]
iftoInt
succeedsNone
if thetoInt
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]
isOption[Int]
A
is the rawInt
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).
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 optionalityFuture
is a monad that models latency as an effectTry
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 inputWriter
is a monad that models logging as an effectState
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
.
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.
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
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 implementsflatMap
(in addition tomap
) is a monad. - In the preceding discussion I used
Option
for my examples, but you can also use instances ofTry
orEither
, 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
EXTRA CREDIT: More information about effects from the Cats Effect website
I thought that since the Cats Effect website had the word “effect” in the name, they might have some additional good descriptions of what an effect is, and sure enough they do. Here are several links from their website, followed by the relevant quotes:
“(Cats Effect) provides a concrete tool, known as ‘the IO
monad,’ for capturing and controlling actions, often referred to as “effects...”
typelevel.org/cats-effect/docs/concepts:
“An effect is a description of an action (or actions) that will be taken when evaluation happens.”
def foo(str: String): IO[String] = ???
“In the above snippet, printer
and printAndRead
are both effects: they describe an action — or in the case of printAndRead
, actions plural — which will be taken when they are evaluated. foo
is an example of a function which returns an effect. As a short-hand, such functions are often called ‘effectful’: foo
is an effectful function.”
“Critically, a side-effect is not the same thing as an effect. An effect is a description of some action, where the action may perform side-effects when executed. The fact that effects are just descriptions of actions is what makes them much safer and more controllable. (Conversely, when a piece of code contains a side-effect, that action just happens.)”
“In Cats Effect, code containing side-effects should always be wrapped in one of the ‘special’ IO
constructors.”
(Note: I modified those last two quotes a little bit.)
typelevel.org/cats-effect/docs/2.x/datatypes/io:
“IO
is a data type for encoding side effects as pure values, capable of expressing both synchronous and asynchronous computations.”
“A value of type IO[A]
is a computation which, when evaluated, can perform effects before returning a value of type A
.”
“IO
values are pure, immutable values, and thus preserve referential transparency, being usable in functional programming. An IO
is a data structure that represents just a description of a side effectful computation.”
EXTRA CREDIT: Effect/Effectful quotes from the Philip Wadler paper
In addition to those quotes, the Philip Wadler paper titled, “Monads for functional programming” (available as a PDF, which you can search for) has these quotes about effects:
“The use of monads to structure functional programs is described. Monads provide a convenient framework for simulating effects found in other languages, such as global state, exception handling, output, or non-determinism.”
“Pure languages are easier to reason about”
“These notes describe one (recent advance), the use of monads to integrate impure effects into pure functional languages.”
“Pure functional languages have this advantage: all flow of data is made explicit. And this disadvantage: sometimes it is painfully explicit.”
“A program in a pure functional language is written as a set of equations. Explicit data flow ensures that the value of an expression depends only on its free variables. Hence substitution of equals for equals is always valid, making such programs especially easy to reason about.”
“This programming style regains some of the flexibility provided by various features of impure languages.”
Summary #2
Again, I hope these quotes from the Cats Effect website and the Wadler paper help to expand on my description of effect and effectful.