Can anyone explain why not "functional core, functional shell"? Is this partly because the shell has to mostly interact with things like UI APIs, file system APIs, network APIs and database APIs that are usually implemented in an imperative way already so you'd make your life harder this way? What if the APIs were written in a functional style?
For example, I find in TypeScript, most libraries and the language itself are not written with immutability in mind so there's constant gotchas. So while I strongly prefer immutable data structures, going against the grain to use immutability everywhere usually isn't worth the frustration. That for me doesn't mean immutability is a bad idea though, it's more that the libraries and languages support is lacking.
> Can anyone explain why not "functional core, functional shell"? Is this partly because the shell has to mostly interact with things like UI APIs, file system APIs, network APIs and database APIs that are usually implemented in an imperative way already so you'd make your life harder this way? What if the APIs were written in a functional style?
Fundamentally the user isn't functional - if you ask them what they want to do they'll say different things at different times. You can have a 100% purely functional programming language that works like a calculator (with no memory) - the user can put in expressions and the language will evaluate them - but generally users want their language to be able to do things that are non-idempotent and non-time-symmetric and so on. You can, and should, push that part to the edge, but it needs to be there somewhere.
What you say is true, but FP has many different ways of dealing with user input, environment, IO, that are no more complex than imperative procedures (arguably the FP equivalent is simpler).
Such as? Haskell-style IO types have no real denotational semantics (they don't even have a notion of equality), they're useful for jamming imperative instructions into an expression model but they don't actually make them any less imperative, and the only other models of user input I've seen are even more obtuse and harder to work with. If something can only be understood operationally, representing it as an imperative procedure is probably the least bad approach IME (and diving into a "the C language is purely functional" style tarpit is far worse; look at SBT for where that leads).
Not an issue with F#. It’s trivial to exit ’purely functional’ domain, write it like ’pythonic C#’ and still end up with a program that is correct out-of-the-box.
1) IMO as a community we still haven’t quite figured out the best abstractions for functional UIs. Various FRP and Elm-like libraries are good, but they have drawbacks.
2) Not all frontends have functional APIs, you may be stuck writing a lot of FFI glue code.
3) Front end code often gets pushed to devs with different experience and skill sets, in my experience they’re probably less likely to have functional or hard CS backgrounds.
4) Harder recruiting if all the devs need to be proficient with FP.
I personally don’t find any of these reasons compelling enough to negate all of the benefits of FP.
Answers saying FP isn’t suited for IO or for interaction with users are rhetorically interesting but contradict my experience.
Your comments suggest a frontend perspective. But on the backend there's a ton of code (including business logic, which theoretically should be the most important bit).
I wouldn't be qualified to argue the merits of FP on frontend, but on the backend it really seems to have value.
Pure functions can't perform side effects, but they can describe them.
For a functional shell, your program's entry point would be composed of pure functions describing effectful actions to take. The necessary computations get bubbled up to the entry point during evaluation, at which point the runtime / compiled program executes them.
(At least, this is the abstracted perspective you should view the purely functional source code from. The final program itself in practice is, of course, effectful throughout).
It's also fun to note that the IO monad in some perspectives is an abstraction for "functional core, imperative shell" moving as much of the imperative "order matters" computations to an outer "shell". The IO monad in some ways is the way of mathematically representing the "shell" of an application.
The problem with monads is that they're a poor abstraction. Need to output: WriterMonad. Need input: ReaderMonad. Need to store something: StateMonad. Local mutation: MonadST. IO/global mutation: MonadIO. Transactions: MonadSTM. And to combine them: a stack of monad transformers. How delightful!
This is why there's now more focus on algebraic effects as an alternative for keeping a functional core, imperative shell. Monads/transformers are just an ugly solution that even hardcore functional programmers don't want to use.
In .net, most UI stuff is easy on C# and semi-painfull on F# because of limited tooling and examples. I don’t see any theoretical reason why a usable functional language could not be used everywhere. The ecosystem maturity and tooling are the limiting factors.
For example, I find in TypeScript, most libraries and the language itself are not written with immutability in mind so there's constant gotchas. So while I strongly prefer immutable data structures, going against the grain to use immutability everywhere usually isn't worth the frustration. That for me doesn't mean immutability is a bad idea though, it's more that the libraries and languages support is lacking.