What do you do with effects in imperative languages?

Sometimes I have to use imperative languages like Python just because of the libraries that are available that aren’t available in the npm/PureScript or Haskell ecosystems (in my case, machine learning stuff). I’m wondering what folks here do when they have to write and maintain code in an imperative language? Do you try to twist the language to act like a functional programming language, or just give up and go with the idioms of the language?

In particular, I’m curious about effects. I love having Effect/Aff to manage effectful code, but trying to accomplish the same in an imperative language can sometimes feel like I’m trying too hard to make the language into something it’s not. I’ve seen it done in “mostly” functional languages like async in F# and ZIO in Scala where there is good out-of-the-box support for monads, and I was surprised to read John A De Goes’ opinion that “effect tracking” is worthless without monads (if you trust my summary). Most imperative languages lack the capacity to conveniently work with monadic effect systems, so is senseless then to try to keep track of functions that can have side-effects or can fail? Can anyone point to any successes or failures when trying to separate out effectful code in a language without good support for monads?


I like the “Functional Core, Imperative Shell” approach, though I have yet to use it in any large or team projects.


I know of some imperative languages with effect systems, Nim and D, but I don’t have any experience with them. And I’d partly agree with John De Goes if these are what he was referring to because they don’t track effects, they track purity which means that you could be working in a D code base where everything is effectful because your colleagues didn’t bother annotating pure code. I think it’s better than nothing though because it would allow you to start annotating code and then safely unit test it.


These programming languages can be considered to produce programs that can be evaluated and complete some plan, eg. “construct a shortest path or a proof that destination is unreachable”. They are allowed to fail for numerous reasons, such as machine running them failing to execute an instruction or capsizing to memory limit. You can poke a Haskell program with /proc and cause it to fail despite what reads in the type.

Monads have some relation with linear logic. I and many others have realized they behave in very similar ways, except that monads intentionally preserve a static view of the “world”. That is, they represent changes without reasoning over them.

Every once a while you see people argue you shouldn’t bother with type theory because it doesn’t solve computational complexity or it might not be optimal. (They’re wrong, though it very easily offends people if you tell this to them)

If you have peers who work with you, they may have conventions or informal logic to maintain the code. If you write down this reasoning, this may carry same role as well-used types, it may help in picking up the code later just like how types help.

I’ve been doing frontend work in TypeScript/Angular lately. I try to utilize some functional(ish) type system features that TypeScript exposes without bending the language unnaturally. For example, I like to use string literal types to make my value checks exhaustive. Or chaining map and filter calls instead of using for loops.

I’ve also been doing backend work in Python/Django. So far, functional programming in Python seems like a pipe dream to me. I have used lambda functions in some convenient cases, but that’s pretty much it.

In summary, I try to be functional as much as the language allows me to be and no more.