It's rarely "only crash" as that eventually bubbles up to being equivalent to a reboot. It's simply that you can have subsystems that do that in effective isolation if the effects of a reboot are minimal.
On the code state upgrade side there are usually a few different issues that can arise and I'd be very curious to hear if there are ways Haskell would handle some of these.
The first one is type changes. I might have a record that has a new field added. Now it's not necessarily pretty to upgrade on call with a pattern match or using a code upgrade protocol but it's easily expressed dynamically.
Another is in the interface, like adding new arguments or changing from a synchronous call to an asynchronous one. These are a bit easier to handle via indirection though they show that you'll need to plan your entry/exit points for upgrades carefully (again, OTP has things like gen_server which make this much easier).
If Haskell can manage to get past they type boundary issue then it's really a matter of supporting at least 2 simultaneous versions of code so each process can be scheduled and upgraded in natural course. Handling more than 2 could be of use depending on how aggressively you want to purge, for example, a local rather than fully qualified call can be caught in a closure and passed around as in some value to be called later. These long lived references will need to be handled carefully or you might get some delayed surprises.