Additionally, on cases where you’re not specifying the data of the type itself, you can use static type constraints on members. This doesn’t give you a default implementation like Haskell but you can always provide one as a function.
Btw the monadic threading is still very useful, especially when mixing impure code and mutation (when appropriate). The async and result monads, in F# computation expression form, are particularly useful.
For a good example of async + impure code, check out MailboxProcessor, which is part of the standard F# lib. Works similar to CSP/goroutine + channels/actor models; makes parallelized concurrency and message passing easy. You can also make pure mailboxes easily by recursively passing data forward, but sometimes you don’t want to for memory / gc & alloc reasons.
http://jobjo.github.io//2019/04/24/ocaml-has-some-new-shiny-...
Laziness is a good default for declarative programming. Nix from NixOS is an example of a recent lazy language and benefits greatly from it.
Even in strict languages, lazy behaviour is often added, for example iterators and streams. Mixing effects and mutation with such constructs is also problematic.
I hope it's gotten better.