
The Case for Controlled Side Effects - mightybyte
http://two-wrongs.com/the-case-for-controlled-side-effects
======
overgard
I totally agree with all of this, but I think the hard part of writing side
effect free code is that it often involves more allocations. (Since you're
constantly constructing new values and objects, and transferring them around
on the stack). I think this is why you see a lot more mutation (and bugs) in
C: managing allocations is annoying. Having a garbage collected language
mitigates the burden on the programmer, but if you're writing performance
sensitive code that means you're putting pressure on the garbage collector so
you're going to get more pauses. Not to say you shouldn't write in this style,
you should, but there's a pretty big opportunity for languages to get better
in this regard.

~~~
lectrick
> I think the hard part of writing side effect free code is that it often
> involves more allocations

Are you saying this in spite of the existing implementation of decent
persistent data structures, at least in some languages?
[https://en.wikipedia.org/wiki/Persistent_data_structure](https://en.wikipedia.org/wiki/Persistent_data_structure)

I think the cost in more memory (and with persistent data structures, only the
_difference_ in memory of the change) is more than made up for by fewer bugs,
easier concurrency, and a host of other benefits.

In the light of "mutability always produces more bugs," mutation should be
seen as an _optimization_ , _after_ tests are written which cover behavior and
side effects.

~~~
tomp
> Are you saying this in spite of the existing implementation of decent
> persistent data structures, at least in some languages?

No, s/he's saying that _because of_ the existing implementations of decent
persistent data structures. Practically all of them allocate _way_ more than
mutable data structures - e.g. trie-based vectors/arrays, trie-base hashmaps,
tree-based ordered maps, etc. Essentially each new addition, modification or
removal results in one or more allocations.

~~~
sanderjd
Also, those allocations imply linked, rather than contiguous, data structures,
which (often? always?) imply poor cache interaction, which is a major source
of inefficiency.

Would love some more perspectives on this, as I've just watched a couple
cppcon talks ([0], [1]) and I'm feeling a bit drunk on the kool-aid.

[0]:
[https://www.youtube.com/watch?v=fHNmRkzxHWs](https://www.youtube.com/watch?v=fHNmRkzxHWs)

[1]:
[https://www.youtube.com/watch?v=rX0ItVEVjHc](https://www.youtube.com/watch?v=rX0ItVEVjHc)

~~~
kqr
There are data structures that are a combination of linked and contiguous,
such as the classic rope data structure. For persistent collections, a common
backing data structure is the hash array mapped trie, which is basically a
quickly-navigated tree of arrays.

Somewhere in there is the obvious tradeoff you have to make between access
time and copying time, with regards to the array length.

~~~
sanderjd
Thanks for the info, I didn't realize the connection between hash array mapped
tries and persistent collections. Are you aware of any open source (ie.
linkable) implementations that work that way?

~~~
tomp
Clojure [1] - the crucial implementation is really in classes
BitmapIndexedNode and ArrayNode.

Scala [2] - Scala's source is an incredibly convoluted spaghetti of abstract
classes and traits, but it looks like the main bits of implementation is in
methods get0 and updated0 of HashTrieMap.

There's also a C# implementation somewhere, but it's not a part of a language
library, so it's likely less optimized (except for some boxing/reified
generics, which make it theoretically more efficient). But all these
implementations use the same basic algorithm, so it's irrelevant which one you
study.

[1]
[https://github.com/clojure/clojure/blob/master/src/jvm/cloju...](https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentHashMap.java)

[2]
[https://github.com/scala/scala/blob/2.11.x/src/library/scala...](https://github.com/scala/scala/blob/2.11.x/src/library/scala/collection/immutable/HashMap.scala)

~~~
sanderjd
Wow, thanks for doing the leg work for me. This is the type of thing that
reminds me why the internet is so wonderful :)

------
pron
Pretending that modern type systems provide anything near an adequate control
of effects is misleading at best. Controlled side effects are an excellent
goal; the way Haskell (or the pure-FP approach) goes about achieving that goal
not only falls short of the target (which is OK) but may be going off in the
wrong direction altogether.

For example, one of the important things about side effects is not only their
existence or lack thereof, but their ordering (e.g. you must write to a socket
_after_ you open it and _before_ you close it). While enforcing ordering is
possible with PFP type systems (to a certain extent), it gets very complicated
very fast. On the other hand, other ways of reasoning about effects and their
ordering -- e.g. by debugging and profiling -- are made harder, rather than
easier, by the PFP approach.

Personally, I _believe_ a better approach is imperative-functional, namely
inline (i.e. non-monadic) side-effects, but made explicit with good effect
systems. Sadly, we're not yet at a point where this is possible and easy to
do.

I strongly believe monads are not a good way of representing side effects, and
to understand why you only need to look at how hard it is to compose monads
and how difficult to follow are the ways of composing them (monad
transformers).

~~~
tome
> Pretending that modern type systems provide anything near an adequate
> control of effects is misleading at best

Adequate for what exactly? Programmers all around the world are using Haskell
every day and finding it saves them headaches because it offers them some
benefits with regard to control over effects. It's perfectly adequate for us.
I've been writing Haskell every working day for over two years and one of the
things I most appreciate about it is its effect system.

You keep repeating these kinds of claims but you don't actually offer any
alternative, nor have you offered any compelling explanation for why debugging
and profiling should be _intrinsically_ harder in a pure functional language.

Maybe you can't write an air traffic control system in Haskell. So what? Who
cares? Haskell is a phenomenally useful tool for a growing number of
developers working on a wide range of different tasks (those tasks don't
include air traffic control systems or missile defense systems, but we'll live
with that).

And pray tell, what would an effect system look like that's not monadic? I'm
genuinely interested as I benefit every working day from Haskell's effect
typing and if there's something even better I'd like to know about it!

I'm keenly waiting for your talk to appear here so I can actually try to
understand what you are on about:

[https://www.youtube.com/channel/UC-
WICcSW1k3HsScuXxDrp0w](https://www.youtube.com/channel/UC-
WICcSW1k3HsScuXxDrp0w)

Hope it appears at some point!

~~~
pron
> I've been writing Haskell every working day for over two years and one of
> the things I most appreciate about it is its effect system.

Haskell's type system is not an effect system.

> And pray tell, what would an effect system look like that's not monadic?

Something like this -- [http://research.microsoft.com/en-
us/projects/koka/](http://research.microsoft.com/en-us/projects/koka/) \-- but
with notions of ordering and better composition.

> Hope it appears at some point!

It's up there now.

~~~
tome
> Haskell's type system is not an effect system.

Haskell's type system models effects through things like monads, applicatives,
categories. If you don't agree with this then you're going to have to tell me
what your definition of "effect system" and "type system" are.

> Something like this -- [http://research.microsoft.com/en-
> us/projects/koka/](http://research.microsoft.com/en-us/projects/koka/) \--
> but with notions of ordering and better composition.

I'll give it a look, thanks,

> It's up there now.

Wow, that was good timing!

~~~
pron
> Haskell's type system models effects through things like monads,
> applicatives, categories. If you don't agree with this then you're going to
> have to tell me what your definition of "effect system" and "type system"
> are.

Haskell's type system, as you rightly note, _models_ effects through types on
return values combined with monads. But Haskell code -- since the language is
pure -- by definition has no effects. It triggers effects in the runtime by
returning monadic values, but it doesn't actually have effects (if it did, it
wouldn't have been pure). An effect system[1] describes the effect _impure_
functions have rather than the types of the values they return (i.e. they
don't require effects to be modeled as values). An example of an effect system
is Java's checked exceptions, or Koka's effects.

> Wow, that was good timing!

You'll be disappointed if you're expecting a discussion of effect systems...

[1]:
[https://en.wikipedia.org/wiki/Effect_system](https://en.wikipedia.org/wiki/Effect_system)

~~~
tome
> > Wow, that was good timing!

> You'll be disappointed if you're expecting a discussion of effect systems...

I'm expecting a discussion of why continuations are enough.

~~~
pron
That's discussed but without a proof. I'm working on a blog post (to be
published today or tomorrow) which links to the proof and provides a more in-
depth discussion.

~~~
tome
Great, I'm looking forward to it!

------
adrusi
The D programming language has compiler support for this. Unfortunately due to
legacy concerns it's opt-in, which makes it much less useful, and the
designers have said that they regret not adding it to the language earlier
when it could have been mandated.

It has the `pure' keyword, which can be used to annotate functions that don't
have any side-effects _other than mutating its arguments_. If you want to
disallow mutating arguments, you can just const them up. In the article's
example, you could probably eliminate a lot digging through code by only
looking at the functions without a `pure' annotation and where
`profile.verified_email' is accessible in a non-const argument.

The D designers call the property that the `pure' annotation enforces "weak
purity" and I think it's a really useful notion for managing side effects in
imperative languages. In C, for example, it just means that you can't mutate
global (or local static) variables, or mutate anything through them (no
mutating the value pointed at by a global).

Shame it didn't make it into Rust. I guess the rationale was that it made
lambdas too complicated and didn't reall come with much benefit (there aren't
many more ways to violate weak purity in Rust than in C, and Rust actually
forbids mutable globals anyway).

------
wheaties
That is a symptom of mutable code. In that case, side-effects are nearly a red
herring because if the object were immutable, then you would really only ever
have to check once.

~~~
Guvante
Mutation is one of many side-effects. Side-effect tracking is a solution to
more than just mutation.

~~~
mden
Could you give an example of a dangerous side-effect that is not a mutation?

~~~
mdpopescu
Printing out stuff; playing a song; starting a script.

------
ak4g
> I don't like wasting my time on things my computer could do for me.

So don't. Let the interpreter check profile.verified_email twice (horror of
horrors!) and get back to your day job.

Even if there are _no_ in-program side effects, removing a 2nd check for
verified_email immediately before preparing and sending the email introduces a
race condition: the user's email is verified, your first check runs, then you
go talking to the DB and who knows what else for the last_interaction
fetch/business logic, meanwhile, user changes their email address, and now
profile.verified_email is false. By skipping your 2nd if check, you send your
email to an address that is still pending verification. This assumes that
.verified_email is a property that talks to the DB, and while it'd be better
style in that case to make it an .is_verified_email() method to make that
clear, doing so is a plausible and not-incorrect use of properties.

It's not worth your (employer's) time to even _check_ if that's the case.
Leave the non-broken code as-is and return to your regular programming.

~~~
radicalbyte
Add a comment so the next person reading the code can understand why it's
built that way.

------
eldude
I agree with the need/value of side-effect/pure annotations. From a debugging
perspective though, there are 2 alternative solutions: Aspect Oriented
Programming (i.e., mutation observations) and Immutable data types (e.g.,
Object freezing) to cause an exception on mutation.

Depending on the language, you can replace the value with a watcher or proxy
and add a break point to know if it was mutated between those lines.
Alternatively, you could freeze the object or property or make it immutable
between those lines to cause an exception to be thrown. Both will require
triggering the case in question live so annotations still superior, especially
compared to manually reading code.

------
joeblubaugh
I didn't see this addressed in the article:

Do these annotations have force? I've run into plenty of annotations that lie
- how can I trust that code that's annotated as being side-effect-free
actually _is_?

~~~
adrusi
Maybe the author, presumably well versed in type systems, assumed that the use
of _effect typing_ was implied. If you're not familiar go check it out, but
I'll give a synopsis. If you're familiar with `unsafe' in Rust, it's a
specific case of effect typing.

You can annotate a function with an effect (useful examples: unsafe, impure,
throws exceptions) and then if another function calls that function, then it
needs to also be annotated with that effect, or else the compiler emits and
error. Usually there will also be an escape hatch where you can assert to the
compiler that you know, in a way that it can't statically prove, that a
certain call to, say, an impure function, still doesn't have side effects.
Code that appears within these escape hatches needs to be heavily tested in
order for effect typing to be useful.

For the above examples of effect types, the compiler will also consider
certain non-function-call constructs to have an effect annotation.
Dereferencing an arbitrary integer as a pointer, for example, would
effectively have an "unsafe" annotation.

I believe Nim has a generalized implementation of effect typing.

------
cwp
I was waiting for the paragraph that read "I'm not talking about pure
functional languages because X, nor am I talking about Rust, because Y."

So, yeah, uncontrolled side-effect are a PITA. Solutions exist.

------
ericfrederich
He mentions Python, is there a solution to this in Python? Perhaps Python 3.5
with optional type checking annotations?

------
patsplat
Wonder how many klocs are underneath that `profile.email.interactions()`
method?

------
michaelochurch
Welcome to Haskell.

No, seriously. We allow side effects. We have the STM monad, the ST (single-
threaded) monad, and the IO monad if you really want to go wild. And almost no
one seriously argues that the IO monad is "dirty" and should be avoided. In
fact, the type signature of a top-level executable's main is IO () by mandate.

I want to make a proposal, which is that we ban the term "side effect". It
doesn't mean what people think it does.

For example, the print function puts output on the console but that's not a
"side effect"; that's _the main effect_. I would argue also that non-
detectable stateful effects (e.g. building up a large dictionary mutably)
shouldn't be called "side effects" (although they are _stateful_ effects) if
they're purely implementation details that are hidden behind an interface.

"Side effect", I think, is a case of a term that used to resemble either a
design flaw or a non-intuitive behavior (i.e. reading your command-line
arguments deletes the strings, which is something that I encountered in one
system) that is tolerated for performance's sake; but that has now been
expanded to include stateful main (i.e. desired) effects as well.

~~~
tome
"Side effect" means something that happens implicitly as a result of
evaluation. There's no need to ban the term; we just need to keep in mind how
it differs from the more general notion of "effect".

