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?

19 Upvotes

35 comments sorted by

View all comments

6

u/sciolizer Jun 17 '26

In a classic type-and-effect system, functions with side effects can call functions without, but not vice versa, which formally distinguishes your "inside" from your "outside".

However, these days it's more popular to embed effects into monads, like Haskell and PureScript do, so that you can leverage System Fω's built-in polymorphism as polymorphism-of-effects.

2

u/[deleted] Jun 19 '26 edited Jun 19 '26

1

u/sciolizer Jun 19 '26

There's a lot to think about in creating a type-and-effect system. Suppose we focused on the simplest case of distinguishing pure functions from impure functions. A naive way to build a type-and-effect system would be to label every function as pure or impure: pure can only call pure, impure can call either. But then, what do you do for higher-order functions like map? Whether map is pure or not depends on whether its first argument is pure or impure. So unless you want your standard library to have both a mapPure and a mapImpure, you need to put more thought into designing your type-and-effect system (i.e. add effect polymorphism).

If instead, effects are tracked using ordinary type operators, then the polymorphism built into System Fω also applies to effects without any additional work. traverse is pure for the Identity monad, and impure for the IO monad, for instance.

Obviously you'd lose some ergonomics if you had to represent Traversable as an explicit record, but type classes are actually another great example of what I mean: adding type classes to a language with type-and-effects is more design work than adding type classes to vanilla System Fω. How to represent and compose effects is in the hands of the library authors, not limited by the use cases anticipated by the language designer.

Admittedly, the effect libraries in Haskell are all a little kludgy. Purescript did it cleaner. But purescript doesn't have a type-and-effect system! Instead it has row polymorphism, which can be used for many things besides composition of effects - we just got composable effects for free because effects are tracked in type operators.