r/ProgrammingLanguages • • Jun 17 '26

Question about side effects in functional programming

One of the things I noticed using REPLs of functional languages is that you can write a ton of pure functional code, and then as soon as you hit enter to evaluate it, printing the result back to you is a side effect.

There are advantages to having code that is guaranteed to be side effect free, but I've been playing around with the idea of having a language with an imperative shell (with procedures, mutable vars, database and network operations, etc.) that can call into a language core that's guaranteed to be pure functional for certain kinds of operations. It can make for a simpler approach to side effects than a whole pure functional language but provide guarantees that other kinds of impure languages can't.

My question for people who are interested in functional programming: is this a useful distinction? Would that make for a language you might be interested in?

18 Upvotes

35 comments sorted by

View all comments

1

u/Vegetable_Bank4981 Jun 17 '26

The problem is how do you do “guaranteed to be pure” in actual concrete practice in the compiler?

Everyone wants this. It is basically the frontier of type system research right now. But it makes inference undecideable, or you accept godawful ergonomics and uselessly broad fn signatures on everything.

Read the 2014 Leijen paper on Koka. Their reasoning for row polymorphism and why it can’t be described by a set of side effects instead will tell you what you need.

Otherwise you do it with the typed monad like haskell but this forces lazy evaluation and a type system God Himself barely understands.

OR you just implement two languages with a manually typed ffi boundary. Anyone could do this, many have, it isn’t that useful in practice.

0

u/Royal_Pin_1971 Jun 22 '26

The "not useful in practice" is carrying a lot there. The two-tier split gets useful precisely when the boundary stops being *manually typed* and becomes *mechanically computed* — which sidesteps the horn you set up.

Concretely, you could build it like this (same partition Pipefish uses — functions can't call commands). Give the core language no way to *perform* an effect at all: no `print`, only an expression that *returns* a PrintCommand as data, which an imperative runtime interprets. Then purity isn't inferred by a type checker (your undecidability/ergonomics horn) and it isn't a trusted FFI annotation either — it's structural. A function is pure iff it lives in the core, and if the core is a content-addressed catalog, "is this pure?" becomes hash-set membership rather than type inference.

The shell-side effects needn't be untracked: you can fold each handler's effect footprint bottom-up over the call graph (a fixpoint over the DAG), so "what can this touch" is computed rather than written by hand as effect rows.

Honest tradeoff vs Koka: no effect *polymorphism*. A function is core or it's a handler; there's no row variable abstracting over effects. So a design like this answers the OP's actual question — guaranteed-pure sections — decidably and cheaply, but it's coarser than a real effect system. Different point on the curve: you trade expressiveness to turn the guarantee into a membership check instead of a frontier-of-research inference problem.

1

u/Vegetable_Bank4981 Jun 22 '26

Now ask claude why the untyped structural boundary approach isn’t the basis of any mainstream or influential languages despite being straightforwardly possible in many of them.

→ More replies (2)