As someone who's written a pure functional framework for C# [1], I'll bite...

> How can you "Make illegal states unrepresentable" with mutable state and sequences of mutations that cannot be enforced with the type system?

Firstly, don't use mutable state, write immutable types. Secondly, write constructors that reject poorly formed data structures. Thirdly, for existing libraries with types that are mutable, create a wrapper for the library with functions that return an IO/effect monad.

> How can you do "Errors as values" at a large scale without do-notation / monads?

Luckily, from a C# PoV, we have LINQ, which is equivalent to do-notation. I agree that manual management of monadic flow would be hard without something akin to do-notation or LINQ.

You can get quite far with fluent methods, but a general monadic-bind is quite hard to chain if you want to carry all of the extracted values through to subsequent expressions (lots of nesting), so yeah, it would not be ideal in those languages. It should be stated that plenty of functional languages also don't have do-notation equivalents though.

> How can you do "Functional core, imperative shell" without the ability to create mini DSLs and interpreters in your language?

I've never really liked the "Functional core, imperative shell" thing. I think it's an admission that you're going to give up trying to be functional when it gets difficult (i.e. interacting with the real world). It is entirely possible to be functional all the way through a code-base.

In terms of DSLs: I'm not sure I know any language that can't implement a DSL and interpreter. Most people don't realise that the Gang of Four Interpreter pattern is isomorphic to free-monads, so most imperative languages have the ability to do the equivalent of free-monads.

As the GP states, it takes discipline to stick to the constraints that a language like Haskell imposes by default. Not sure about the charisma part!

I have found that having access to a world-class compiler, tooling, and large ecosystem to be more valuable to getting shit done than the exact language you choose. So, bringing the benefits of the pure-FP world into the place where I can get shit done is better than switching to, say Haskell, where it's harder to get shit done due to ecosystem limitations.

There's also the get out of jail free card, which allows me to do some gnarly high-performance code in an imperative way. And, as long as I wrap it up in a function that acts in a referentially transparent way, then I can still compose it with the rest of my pure code without concern. I just need to be a bit more careful when I do that and make sure it's for the right reasons (i.e. later stage optimisations). That's less easy to do in FP languages.

Again, it's about discipline.

If you want to see how this can look in a mainstream, imperative-first, language. I have a few samples in the repo, the one I like to share when helping OO-peeps learn about monads and monad-transformers is this game of 21/Pontoon [2]. I suspect most people won't have seen C# look like this!

[1] https://github.com/louthy/language-ext/

[2] https://github.com/louthy/language-ext/blob/main/Samples/Car...