MonadError should not be use anymore? - haskell

I have a question about MonadError, namely instead of using MonadError, it is better to use Except?

MonadError and Except are not mutually exclusive. They are things of different kinds.
MonadError is a type class (i.e. an "interface") that defines certain operations (throwError and catchError), which may be implemented by different monads. If a function has a type signature requiring MonadError, it means "I will work in any monad, as long as it implements MonadError, because I make use of the operations defined in it", for example:
f :: MonadError MyErrorType m => Int -> m String
Except, on the other hand, is a specific monad that has a specific underlying implementation. This specific monad happens to implement the operations from MonadError, which means that any function that requires MonadError may be used in Except:
runExcept $ f 42 -- using the definition of `f` above
You could also declare your function to be used specifically in Except, as opposed to "in anything that implements MonadError":
g :: Int -> Except MyErrorType String
Such function can still be used in Except like f can:
runExcept $ g 42
But it cannot be used in other monads that implement MonadError.
The practical consequence of this is that if you're writing some local, small, simple program, it's ok to write all your functions in Except, but if you're writing a bigger system with a lot of modules, or better yet, a reusable library, you're better off writing your functions in MonadError, because then your consumers will have more freedom in consuming your library. In OO world this idea is known as "programming to interface".
One possible drawback of this is that the code might (not always) come out less performant, because the compiler may not be able to do enough inlining without knowing the final types of everything. In practice, however, the performance penalty is trivial in most applications. The usual maxim applies: first measure, then optimize.

Related

Should I prefer MonadUnliftIO or MonadMask for bracketting like functions?

I'm currently building a new API, and one of the functions it currently provides is:
inSpan :: Tracer -> Text -> IO a -> IO a
I'm looking to move that Tracer into a monad, giving me a signature more like
inSpan :: MonadTracer m => Text -> m a -> m a
The implementation of inSpan uses bracket, which means I have two main options:
class MonadUnliftIO m => MonadTracer m
or
class MonadMask m => MonadTracer m
But which should I prefer? Note that I'm in control of all the types I've mentioned, which makes me slightly lean towards MonadMask as it doesn't enforce IO at the bottom (that is, we could perhaps have a pure MonadTracer instance).
Is there anything else I should consider?
Let's lay out the options first (repeating some of your question in the process):
MonadMask from the exceptions library. This can work on a wide range of monads and transformers, and does not require that the base monad be IO.
MonadUnliftIO from the unliftio-core (or unliftio) library. This library only works for monads with IO at their base, and which is somehow isomorphic to ReaderT env IO.
MonadBaseControl from the monad-control library. This library will require IO at the base, but will allow non-ReaderT.
Now the tradeoffs. MonadUnliftIO is the newest addition to the fray, and has the least developed library support. This means that, in addition to the limitations of what monads can be instances, many good instances just haven't been written yet.
The important question is: why does MonadUnliftIO make this seemingly arbitrary requirement around ReaderT-like things? This is to prevent issues with lost monadic state. For example, the semantics of bracket_ (put 1) (put 2) (put 3) are not really clear, and therefore MonadUnliftIO disallows a StateT instance.
MonadBaseControl relaxes the ReaderT restriction and has wider library support. It's also considered more complicated internally than the other two, but for your usages that shouldn't really matter. And it allows you to make mistakes with the monadic state as mentioned above. If you're careful in your usage, this won't matter.
MonadMask allows totally pure transformer stacks. I think there's a good argument to be had around the usefulness of modeling asynchronous exceptions in a pure stack, but I understand this kind of approach is something people want to do sometimes. In exchange for getting more instances, you still have the limitations around monadic state, plus the inability to lift some IO control actions, like timeout or forkIO.
My recommendation:
If you want to match the way most people are doing things today, it's probably best to choose MonadMask, it's the most well adopted solution.
If you want that goal, but you also need to do a timeout or withMVar or something, use MonadBaseControl.
And if you know there's a specific set of monads you need compatibility with, and want compile time guarantees about the correctness of your code vis-a-vis monadic state, use MonadUnliftIO.

Flatten monad stack

So I have this sort of code all over my first serious haskell project:
f :: (MonadTrans t) => ExceptT () (t (StateT A B)) C
f = do mapExceptT lift $ do
lift $ do
...
lift $ do
...
r <- ...
...
return r
>>= \r -> ...
There definitely may be something wrong about how I try to achieve my goals (there might be simpler ways how to do it) but currently I am interested in learning how to handle a stack of monad transformers in some nicer way, if there is one. This is the only way I figured out how to get r in the context of B and lift it to a monad higher in the stack. Lifting whole blocks instead of initial statements is as far as I could get on my own.
What I also often end up with are chains of lift which I found out can be avoided with liftIO if the deep monad is IO. I am not aware of a generic way for other monads though.
Is there a pattern that one can follow when he ends up dealing with such stacks, and having to extract one value at some level, a different value at a different level, combine these and affect any of the two levels or maybe yet another one?
Can the stack be manipulated somehow without neither lifting whole blocks (which causes let and bound variables to be scoped and restricted to the inner block) nor having to lift . lift . ... lift individual actions?
This is a well-known problem with monad transformers in general. Researchers have devised various ways of handling it, none of which is clearly "best". Some of the known solutions include:
The mtl approach, which automatically lifts type classes of monads over its built-in monad transformers (and only its built-in monad transformers). This allows you to just write f :: (MonadState A m, MonadError () m) => m C if those are the only features of the monad that your function is using. Due to its extreme non-portability and a few other reasons, mtl is generally considered pseudo-deprecated. See this page and this question for the gory details.
If you have a particular monad stack you are using over and over again, you can wrap it in a newtype and write instances of the various monad type classes it supports manually. For Functor, Applicative, Monad, and any other type classes implemented by the top-level transformer in your stack, you can use GeneralizedNewtypeDeriving to have the compiler write the instances for you automatically; for other type classes, you will have to insert the appropriate number of lift calls for each method. The advantage of this approach is that it's more general and simpler to understand while giving you the same flexibility at the call site as mtl. The big problem with this approach is that it encourages using a single "mega-monad" for all operations rather than specifying only the needed operations, since adding any new monad transformer to the stack requires writing a whole new list of instances.
In most cases, you don't really want a monad that has "some arbitrary state of type A" and "some arbitrary exception-throwing capability". Rather, the different features offered by your monad stack have some semantic meaning in your mental model of your program. A variation of the previous approach is to create custom type classes for the effects beyond the basic Functor, Applicative, and Monad and write instances for the custom type classes on your newtype'd monad instead. This has a major advantage over the other approaches listed here: you can have a stack with multiple copies of the same monad transformer in it at different positions. This is the strategy I've used the most in my own programs so far.
A completely different approach is effect systems. Normally, an effect system has to be built in to a language's type system, but it's possible to encode an effect system in Haskell's type system. See the effect-monads package.
The usual approach is to use the mtl library rather than using transformers directly. I'm not sure what the story behind your t is, but the usual mtl approach is to use very general type signatures at definition sites, like
foo :: (MonadError e m, MonadState s m) => m Int
Then fix the actual transformer stacks at the call sites. A common recommendation is to wrap up the stack in a newtype to avoid muddying things up where they're used.
If this isn't your style (and it's not for everyone), you can still use the mtl methods to perform operations, while giving an explicit transformer stack. This should cut down on the manual lifting substantially. The advantage of this approach is that it gives you a better view of the interactions of effects at the definition sites; the disadvantage is that more code needs all the information.

Why are instances matched only by their heads?

I'll start by introducing a concrete problem (StackOverflow guys like that).
Say you define a simple type
data T a = T a
This type is a Functor, Applicative and a Monad. Ignoring automatic deriving, to get those instances you have to write each one of them, even though Monad implies Applicative, which implies Functor.
More than that, I could define a class like this
class Wrapper f where
wrap :: a -> f a
unwrap :: f a -> a
This is a pretty strong condition and it definitely implies Monad, but I can't write
instance Wrapper f => Monad f where
return = wrap
fa >>= f = f $ unwrap fa
because this, for some reason, means "everything is a Monad (every f), only if it's a Wrapper", instead of "everything that's a Wrapper is a Monad".
Similarly you can't define the Monad a => Applicative a and Applicative a => Functor a instances.
Another thing you can't do (which is only probably related, I really don't know) is have one class be a superclass of another one, and provide a default implementation of the subclass. Sure, it's great that class Applicative a => Monad a, but it's much less great that I still have to define the Applicative instance before I can define the Monad one.
This isn't a rant. I wrote a lot because otherwise this would quickly the marked as "too broad" or "unclear". The question boils down to the title.
I know (at least I'm pretty sure) that there is some theoretical reason for this, so I'm wondering what exactly are the benefits here.
As a sub question, I would like to ask if there are viable alternatives that still keep all (or most) of those advantages, but allow what I wrote.
Addition:
I suspect one of the answers might be something along the lines "What if my type is a Wrapper, but I don't want to use the Monad instance that that implies?". To this I ask, why couldn't the compiler just pick the most specific one? If there is an instance Monad MyType, surely it's more specific than instance Wrapper a => Monad a.
There's a lot of questions rolled into one here. But let's take them one at a time.
First: why doesn't the compiler look at instance contexts when choosing which instance to use? This is to keep instance search efficient. If you require the compiler to consider only instances whose instance heads are satisfied, you essentially end up requiring your compiler to do back-tracking search among all possible instances, at which point you have implemented 90% of Prolog. If, on the other hand, you take the stance (as Haskell does) that you look only at instance heads when choosing which instance to use, and then simply enforce the instance context, there is no backtracking: at every moment, there is only one choice you can make.
Next: why can't you have one class be a superclass of another one, and provide a default implementation of the subclass? There is no fundamental reason for this restriction, so GHC offers this feature as an extension. You could write something like this:
{-# LANGUAGE DefaultSignatures #-}
class Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
default pure :: Monad f => a -> f a
default (<*>) :: Monad f => f (a -> b) -> f a -> f b
pure = return
(<*>) = ap
Then once you had provided an instance Monad M where ..., you could simply write instance Applicative M with no where clause and have it Just Work. I don't really know why this wasn't done in the standard library.
Last: why can't the compiler allow many instances and just pick the most specific one? The answer to this one is sort of a mix of the previous two: there are very good fundamental reasons this doesn't work well, yet GHC nevertheless offers an extension that does it. The fundamental reason this doesn't work well is that the most specific instance for a given value can't be known before runtime. GHC's answer to this is, for polymorphic values, to pick the most specific one compatible with the full polymorphism available. If later that thing thing gets monomorphised, well, too bad for you. The result of this is that some functions may operate on some data with one instance and others may operate on that same data with another instance; this can lead to very subtle bugs. If after all this discussion you still think that's a good idea, and refuse to learn from the mistakes of others, you can turn on IncoherentInstances.
I think that covers all the questions.
Consistency and separate compilation.
If we have two instances whose heads both match, but have different constraints, say:
-- File: Foo.hs
instance Monad m => Applicative m
instance Applicative Foo
Then either this is valid code producing an Applicative instance for Foo, or it's an error producing two different Applicative instances for Foo. Which one it is depends on whether a monad instance exists for Foo. That's a problem, because it's difficult to guarantee that knowledge about whether Monad Foo holds will make it to the compiler when it's compiling this module.
A different module (say Bar.hs) may produce a Monad instance for Foo. If Foo.hs doesn't import that module (even indirectly), then how is the compiler to know? Worse, we can change whether this is an error or a valid definition by changing whether we later include Bar.hs in the final program or not!
For this to work, we'd need to know that all instances that exist in the final compiled program are visible in every module, which leads to the conclusion that every module is a dependency of every other module regardless of whether the module actually imports the other. You'd have to go quite far along the path to requiring whole-program-analysis to support such a system, which makes distributing pre-compiled libraries difficult to impossible.
The only way to avoid this is to never have GHC make decisions based on negative information. You can't choose an instance based on the non-existence of another instance.
This means that the constraints on an instance have to be ignored for instance resolution. You need to select an instance regardless of whether the constraints hold; if that leaves more than one possibly-applicable instance, then you would need negative information (namely that all but one of them require constraints that do not hold) to accept the code as valid.
If you have only one instance that's even a candidate, and you can't see a proof of its constraints, you can accept the code by just passing the constraints on to where the instance is used (we can rely on getting this information to other modules, because they'll have to import this one, even if only indirectly); if those positions can't see a required instance either, then they'll generate an appropriate error about an unsatisfied constraint.
So by ignoring the constraints, we ensure that a compiler can make correct decisions about instances even by only knowing about other modules that it imports (transitively); it doesn't have to know about everything that's defined in every other module in order to know which constraints do not hold.

What functionality do you get for free with Functors or other type-classes?

I read an article which said:
Providing instances for the many standard type-classes [Functors] will immediately give you a lot of functionality for practically free
My question is: what is this functionality that you get for free (for functors or other type-classes)? I know what the definition of a functor is, but what do I get for free by defining something as a functor/other type-class. Something other than a prettier syntax. Ideally this would be general and useful functions that operate on functors/other type-classes.
My imagination (could be wrong) of what free means is functions of this sort: TypeClass x => useful x y = ..
== Edit/Additition ==
I guess I'm mainly asking about the more abstract (and brain boggling) type-classes, like the ones in this image. For less abstract classes like Ord, my object oriented intuition understands.
Functors are simple and probably not the best example. Let's look at Monads instead:
liftM - if something is a Monad, it is also a Functor where liftM is fmap.
>=>, <=<: you can compose a -> m b functions for free where m is your monad.
foldM, mapM, filterM... you get a bunch of utility functions that generalize existing functions to use your monad.
when, guard* and unless -- you also get some control functions for free.
join -- this is actually fairly fundamental to the definition of a monad, but you don't need to define it in Haskell since you've defined >>=.
transformers -- ErrorT and stuff. You can bolt error handling onto your new type, for free (give or take)!
Basically, you get a wide variety of standard functions "lifted" to use your new type as soon as you make it a Monad instance. It also becomes trivial (but alas not automatic) to make it a Functor and Applicative as well.
However, these are all "symptoms" of a more general idea. You can write interesting, nontrivial code that applies to all monads. You might find some of the functions you wrote for your type--which are useful in your particular case, for whatever reason--can be generalized to all monads. Now you can suddenly take your function and use it on parsers, and lists, and maybes and...
* As Daniel Fischer helpfully pointed out, guard requires MonadPlus rather than Monad.
Functors are not very interesting by themselves, but they are a necessary stepping stone to get into applicative functors and Traversables.
The main property which makes applicative functors useful is that you can use fmap with the applicative operator <*> to "lift" any function of any arity to work with applicative values. I.e. you can turn any a -> b -> c -> d into Applicative f => f a -> f b -> f c -> f d. You can also take a look at Data.Traversable and Data.Foldable which contain several general purpose functions that involve applicative functors.
Alternative is a specialized applicative functor which supports choice between alternatives that can "fail" (the exact meaning of "empty" depends in the applicative instance). Applicative parsers are one practical example where the definitions of some and many are very intuitive (e.g. match some pattern zero-or-more times or one-or-more times).
Monads are one of the most interesting and useful type-classes, but they are already well covered by the other answers.
Monoid is another type-class that is both simple and immediately useful. It basically defines a way to add two pieces of data together, which then gives you a generic concat as well as functionality in the aforementioned Foldable module and it also enables you to use the Writer monad with the data type.
There are many of the standard functions in haskell that require that their arguments implement one or more type-classes. Doing so in your code allows other developers (or yourself) to use your data in ways they are already familiar with, without having to write additional functions.
As an example, implementing the Ord type-class will allow you to use things like sort, min, max, etc. Where otherwise, you would need sortBy and the like.
Yes, it means that implementing the type class Foo gives you all the other functions that have a Foo constraint "for free".
The Functor type class isn't too interesting in that regard, as it doesn't give you a lot.
A better example is monads and the functions in the Control.Monad module. Once you've defined the two Monad functions (>>=) and return for your type, you get another thirty or so functions that can then be used on your type.
Some of the more useful ones include: mapM, sequence, forever, join, foldM, filterM, replicateM, when, unless and liftM. These show up all the time in Haskell code.
As others have said, Functor itself doesn't actually get you much for free. Basically, the more high-level or general a typeclass is (meaning the more things fit that description), then the less "free" functionality you are going to get. So for example, Functor, and Monoid don't provide you with much, but Monad and Arrow provide you with a lot of useful functions for free.
In Haskell, it's still a good idea to write an instance for Functor and Monoid though (if your data type is indeed a functor or a monoid), because we almost always try to use the most general interface possible when writing functions. If you are writing a new function that can get away with only using fmap to operate on your data type, then there is no reason to artificially restrict that function to to Monads or Applicatives, since it might be useful later for other things.
Your object-oriented intuition carries across, if you read "interface and implementation" for "typeclass and instance". If you make your new type C an instance of a standard typeclass B, then you get for free that your type will work with all existing code A that depends on B.
As others have said, when the typeclass is something like Monad, then the freebies are the many library functions like foldM and when.

How do you identify monadic design patterns?

I my way to learn Haskell I'm starting to grasp the monad concept and starting to use the known monads in my code but I'm still having difficulties approaching monads from a designer point of view. In OO there are several rules like, "identify nouns" for objects, watch for some kind of state and interface... but I'm not able to find equivalent resources for monads.
So how do you identify a problem as monadic in nature? What are good design patterns for monadic design? What's your approach when you realize that some code would be better refactored into a monad?
A helpful rule of thumb is when you see values in a context; monads can be seen as layering "effects" on:
Maybe: partiality (uses: computations that can fail)
Either: short-circuiting errors (uses: error/exception handling)
[] (the list monad): nondeterminism (uses: list generation, filtering, ...)
State: a single mutable reference (uses: state)
Reader: a shared environment (uses: variable bindings, common information, ...)
Writer: a "side-channel" output or accumulation (uses: logging, maintaining a write-only counter, ...)
Cont: non-local control-flow (uses: too numerous to list)
Usually, you should generally design your monad by layering on the monad transformers from the standard Monad Transformer Library, which let you combine the above effects into a single monad. Together, these handle the majority of monads you might want to use. There are some additional monads not included in the MTL, such as the probability and supply monads.
As far as developing an intuition for whether a newly-defined type is a monad, and how it behaves as one, you can think of it by going up from Functor to Monad:
Functor lets you transform values with pure functions.
Applicative lets you embed pure values and express application — (<*>) lets you go from an embedded function and its embedded argument to an embedded result.
Monad lets the structure of embedded computations depend on the values of previous computations.
The easiest way to understand this is to look at the type of join:
join :: (Monad m) => m (m a) -> m a
This means that if you have an embedded computation whose result is a new embedded computation, you can create a computation that executes the result of that computation. So you can use monadic effects to create a new computation based on values of previous computations, and transfer control flow to that computation.
Interestingly, this can be a weakness of structuring things monadically: with Applicative, the structure of the computation is static (i.e. a given Applicative computation has a certain structure of effects that cannot change based on intermediate values), whereas with Monad it is dynamic. This can restrict the optimisation you can do; for instance, applicative parsers are less powerful than monadic ones (well, this isn't strictly true, but it effectively is), but they can be optimised better.
Note that (>>=) can be defined as
m >>= f = join (fmap f m)
and so a monad can be defined simply with return and join (assuming it's a Functor; all monads are applicative functors, but Haskell's typeclass hierarchy unfortunately doesn't require this for historical reasons).
As an additional note, you probably shouldn't focus too heavily on monads, no matter what kind of buzz they get from misguided non-Haskellers. There are many typeclasses that represent meaningful and powerful patterns, and not everything is best expressed as a monad. Applicative, Monoid, Foldable... which abstraction to use depends entirely on your situation. And, of course, just because something is a monad doesn't mean it can't be other things too; being a monad is just another property of a type.
So, you shouldn't think too much about "identifying monads"; the questions are more like:
Can this code be expressed in a simpler monadic form? With which monad?
Is this type I've just defined a monad? What generic patterns encoded by the standard functions on monads can I take advantage of?
Follow the types.
If you find you have written functions with all of these types
(a -> b) -> YourType a -> YourType b
a -> YourType a
YourType (YourType a) -> YourType a
or all of these types
a -> YourType a
YourType a -> (a -> YourType b) -> YourType b
then YourType may be a monad. (I say “may” because the functions must obey the monad laws as well.)
(Remember you can reorder arguments, so e.g. YourType a -> (a -> b) -> YourType b is just (a -> b) -> YourType a -> YourType b in disguise.)
Don't look out only for monads! If you have functions of all of these types
YourType
YourType -> YourType -> YourType
and they obey the monoid laws, you have a monoid! That can be valuable too. Similarly for other typeclasses, most importantly Functor.
There's the effect view of monads:
Maybe - partiality / failure short-circuiting
Either - error reporting / short-circuiting (like Maybe with more information)
Writer - write only "state", commonly logging
Reader - read-only state, commonly environment passing
State - read / write state
Resumption - pausable computation
List - multiple successes
Once you are familiar with these effects its easy to build monads combining them with monad transformers. Note that combining some monads needs special care (particularly Cont and any monads with backtracking).
One thing important to note is there aren't many monads. There are some exotic ones that aren't in the standard libraries e.g the probability monad and variations of the Cont monad like Codensity. But unless you are doing something mathematical its unlikely you will invent (or discover) a new monad, however if you use Haskell long enough you'll build many monads that are different combinations of the standard ones.
Edit - Also note that the order you stack monad transformers results in different monads:
If you add ErrorT (transformer) to a Writer monad, you get this monad Either err (log,a) - you can only access the log if you have no error.
If you add WriterT (transfomer) to an Error monad, you get this monad (log, Either err a) which always gives access to the log.
This is sort of a non-answer, but I feel it is important to say anyways. Just ask! StackOverflow, /r/haskell, and the #haskell irc channel are all great places to get quick feedback from smart people. If you are working on a problem, and you suspect that there's some monadic magic that could make it easier, just ask! The Haskell community loves to solve problems, and is ridiculously friendly.
Don't misunderstand, I'm not encouraging you to never learn for yourself. Quite the contrary, interacting with the Haskell community is one of the best ways to learn. LYAH and RWH, 2 Haskell books that are freely available online, come highly recommended as well.
Oh, and don't forget to play, play, play! As you play around with monadic code, you'll start to get the feel of what "shape" monads have, and when monadic combinators can be useful. If you're rolling your own monad, then usually the type system will guide you to an obvious, simple solution. But to be honest, you should rarely need to roll your own instance of Monad, since Haskell libraries provide tons of useful things as mentioned by other answerers.
There's a common notion that one sees in many programming languages of an "infectious function tag" -- some special behavior for a function that must extend to its callers as well.
Rust functions can be unsafe, meaning they perform operations that can potentially violate memory unsafety. unsafe functions can call normal functions, but any function that calls an unsafe function must be unsafe as well.
Python functions can be async, meaning they return a promise rather than an actual value. async functions can call normal functions, but invocation of an async function (via await) can only be done by another async function.
Haskell functions can be impure, meaning they return an IO a rather than an a. Impure functions can call pure functions, but impure functions can only be called by other impure functions.
Mathematical functions can be partial, meaning they don't map every value in their domain to an output. The definitions of partial functions can reference total functions, but if a total function maps some of its domain to a partial function, it becomes partial as well.
While there may be ways to invoke a tagged function from an untagged function, there is no general way, and doing so can often be dangerous and threatens to break the abstraction the language tries to provide.
The benefit, then, of having tags is that you can expose a set of special primitives that are given this tag and have any function that uses these primitives make that clear in its signature.
Say you're a language designer and you recognize this pattern, and you decide that you want to allow user-defined tags. Let's say the user defined a tag Err, representing computations that may throw an error. A function using Err might look like this:
function div <Err> (n: Int, d: Int): Int
if d == 0
throwError("division by 0")
else
return (n / d)
If we wanted to simplify things, we might observe that there's nothing erroneous about taking arguments - it's computing the return value where problems might arise. So we can restrict tags to functions that take no arguments, and have div return a closure rather than the actual value:
function div(n: Int, d: Int): <Err> () -> Int
() =>
if d == 0
throwError("division by 0")
else
return (n / d)
In a lazy language such as Haskell, we don't need the closure, and can just return a lazy value directly:
div :: Int -> Int -> Err Int
div _ 0 = throwError "division by 0"
div n d = return $ n / d
It is now apparent that, in Haskell, tags need no special language support - they are ordinary type constructors. Let's make a typeclass for them!
class Tag m where
We want to be able to call an untagged function from a tagged function, which is equivalent to turning an untagged value (a) into a tagged value (m a).
addTag :: a -> m a
We also want to be able to take a tagged value (m a) and apply a tagged function (a -> m b) to get a tagged result (m b):
embed :: m a -> (a -> m b) -> m b
This, of course, is precisely the definition of a monad! addTag corresponds to return, and embed corresponds to (>>=).
It is now clear that "tagged functions" are merely a type of monad. As such, whenever you spot a place where a "function tag" could apply, chances are you've got a place suitable for a monad.
P.S. Regarding the tags I've mentioned in this answer: Haskell models impurity with the IO monad and partiality with the Maybe monad. Most languages implement async/promises fairly transparently, and there seems to be a Haskell package called promise that mimics this functionality. The Err monad is equivalent to the Either String monad. I'm not aware of any language that models memory unsafety monadically, it could be done.

Resources