In my post on Procedures in a Pure Language, I discuss how even pure functional languages can be used to create procedures that have effects, and how that is how things should be. I propose a little language where these impure procedures can coexist with pure functions in a way that makes the line between pure and impure very clear.
In this post, I propose adding a stricture to this language that ensures that, while procedures can have effects, they cannot have side-effects.
Here’s a quick review of a simple program in this language.
main = (console) -> procedure
let greet = console.println("Hello, Bill")
main are functions which, given a
console argument, return procedures, which can be executed with the
! operator. Let’s call such procedures, which are bound to the object on which they operate, methods.
Now let’s say add this rule to our language: procedures can only execute methods on objects passed to them as arguments.
So procedures have no ability to directly reference or execute any other procedure: there are no built-in procedures like
window, and no ability to directly import services or objects like Java’s
Whoever runs the
main procedure above can be certain it will have no effects outside of those that can be achieved by invoking methods on
console. Since the caller has complete control over these methods, any effects of
main are completely contained.
So these procedures can have effects, but since those affects are contained, they cannot have side-effects.
“Impure” Inversion of Control
So a program can’t actually do anything unless it is provided with an object on which it can invoke methods. To OO programmers this sounds like dependency injection or inversion of control.
We can use functional dependency-injection to achieve inversion of control in this language without the syntactic overhead of manual dependency injection.
main = procedure
Procedures Inside Functions
Since effects are contained, a function can be pure and still use impure functions in its implementation! For example:
greet = (name) ->
mutable output = 
output.push! "Hello, "
greet creates a locally-scoped mutable object,
output, and manipulates it – thereby producing effects. But those effects are contained to the local variable.
A functions may be implemented using temporary internal stateful computations like this and still be pure if these states cannot affect the caller or the outside world.
Since any effects of executing procedures are contained to objects passed to those procedures, we can sandbox their effects.
Presumably, when we run the above program in our hypothetical language, the interpreter will by default pass the a real
console object that will actually print to standard output. But let’s say we have the ability to create simple mutable objects that look someting like this:
mutable mockConsole = object
output:  # empty list
println: (message) -> procedure
Now we can pass a mock console to
main = (console) -> procedure
mockConsole.output // ["Hello, Bob"]
Since all services that the
main function might use to have effects (Network, Filesystem, etc.) must be passed to it, it makes commandline scripts written our language easy to test.
Requiring inversion of control for all dependencies on services that can be used to have effects, gives the caller of the procedure complete control over its effects: effects without side-effects.