---
# System prepended metadata

title: Functional imperative compatibilism

---

# Functional imperative compatibilism

I'm a compatibilist about functional and imperative languages. I think you can have systems with functional purity and systems with shared mutable state in the same language and in close proximity and that this is generally the right way for languages to be.

Examples of languages that combine immutable and mutable paradigms include Rust (*does it by making it very clear what's mutable and what's not while having a flexible enough type system to accomodate both styles of programming*) and, also, I contend (contentiously): We can also include [Unison](https://www.unison-lang.org). Unison presents itself as a functional programming language, I would agree it is one, but something *happened* with Unison:

They developed what were previously called Effects into what they now call [Abilities](https://www.unison-lang.org/docs/fundamentals/abilities/). In short, an Ability is a special kind of parameter that interrupts the flow of execution when called into, can replace its state, and continue execution as desired. That crossed a line. In crossing this line they seemed to show that the line wasn't real. The programmer interacts with abilities *as if they were* implicit mutable parameters or globals. Which is to say, it basically *is* mutability, reconstructed in a functional environment.

Abilities are clearly nice to have. An Ability can be, eg, instrumented with a logger, without the code that uses the Ability needing to know about that. From the inside, the function that uses the Ability treats it like a mutable global. From the outside, there is no mutable global. The language encompasses both views.

And I think we can reach a similar synthesis from the other direction. Take your mutable runtime. Introduce a new pattern for managing mutable globals:

```rust
struct Context<T>(Mutex<T>);
impl<T> Context<T> {
    fn new(v:T)-> Self { Self(Mutex::new(v)) }
}
fn with_context<T, R>(c: &Context<T>, new_value: T, block: impl FnOnce()-> R) -> R
{
    let mut guard = c.0.lock().unwrap();
    let old_value = replace(&mut *guard, new_value);
    drop(guard);
    let ret = block();
    let mut guard = c.0.lock().unwrap();
    *guard = old_value;
    return ret;
}
```
`with_context` makes mutable shared state similarly controllable. It gives us a way of writing code that uses mutable globals as if we're using [implicit/context parameter](https://docs.scala-lang.org/tour/implicit-parameters.html)s (with a default or inherited value) instead. The `with_context` guarantees that the global will be restored to its previous value once the call is complete, and the mutex guarantees that we wont have race conditions, eliminating most of the problems that mutable state generally has. (footnote: *we're left with just the issues of implicit parameters: you might forget you're passing them around, you might not be aware of which function calls are going to use them. Where contextual/implicit parameters are needed for ergonomics, this is something we just have to deal with. Some parameters are noise that can be relegated to context, contextual parameters are fine when and only when they're used in those cases. And you could say this has nothing to do with mutability... but I suppose changes in implicits, even when well managed, are one aspect of mutability, even though implicits first landed triumphantly as [a Haskell extension](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/implicit_parameters.html).*)

```rust
let logger = Context::new(Logger::toStdout());

fn main(){
    send(&logger, "a");
    with_context(
        &logger,
        Logger::to_url("https://logs.dreamshrine.moon"),
        ||{
            send(&logger, "b");
        }
    );
    send(&logger, "c");
}

//logged locally: "a", "c"
//logged on the moon: "b"
```

Combine this with [generators](https://dev-doc.rust-lang.org/beta/unstable-book/language-features/generators.html), it's not clear to me that there would remain any difference between the Unison way and the Rust (with `with_context`) way. We end up in the very similar styles. In the least, mutable shared state is *very similar* to implicit abilities.

As it is in mathematics, there's a lot of value in trying to paraphrase a phenomenon in stranger language, or to model a phenomenon in a domain of thought that isn't usually thought of as being good at modelling it, because it builds a bridge that lets us import all of the techniques we have in those domains. In this case it illuminated much: A better way of managing mutable globals, some of the hazards we might encounter with Abilities, and why we're going to use Abilities anyway.

## What about runtimes?

It's conceivable that there might be incompatibilities in how functional and imperative runtimes would want to be optimized. Functional languages have more use for generational garbage collection (*fewer mutations means more small allocations*), and for sharing memory between threads (*shared memory is generally problematic unless the data being shared is immutable, in which case it's trivial*), and there might be some specialization needed for optimizing a lot of lazy evaluations.

But in the long term, I'd guess incompatibilities are unlikely. It's not as if anyone wouldn't want these things in their imperative/mixed runtimes too. A strong example is the jvm, developed during the imperative age, hyperoptimized for the execution of java (*which was initially so non-functional that it lacked a syntax for lambdas*), but it still has a generational GC and memory sharing between threads!

Also, isn't optimized compiled functional code often basically imperative on the assembly level? (or, shouldn't it be?)

So I think generalist runtimes should win here.

[🦋](https://bsky.app/profile/mako.dreamshrine.org) [🦋](https://bsky.app/profile/mako.dreamshrine.org/post/3lr4scmjq222h)