Hidden Identity: Using the Identity Monad
Last week we announced our new Making Sense of Monads course. If you subscribe to our mailing list in the next week, you can get a special discount for this and our other courses! So don't miss out!
But in the meantime, we've got one more article on monads! Last week, we looked at the "Function monad". This week, we're going to explore another monad that you might not think about as much. But even if we don't specifically invoke it, this monad is actually present quite often, just in a hidden way! Once again, you can watch the video to learn more about this, or just read along below!
On its face, the identity monad is very simple. It just seems to wrap a value, and we can retrieve this value by calling runIdentity
:
newtype Identity a = Identity { runIdentity :: a }
So we can easily wrap any value in the Identity monad just by calling the Identity
constructor, and we can unwrap it by calling runIdentity
.
We can write a very basic instance of the monad typeclass for this type, that just incorporates wrapping and unwrapping the value:
instance Monad Identity where
return = Identity
(Identity a) >>= f = f a
A Base Monad
So what's the point or use of this? Well first of all, let's consider a lot of common monads. We might think of Reader
, Writer
and State
. These all have transformer variations like ReaderT
, WriterT
, and StateT
. But actually, it's the "vanilla" versions of these functions that are the variations!
If we consider the Reader
monad, this is actually a type synonym for a transformer over the Identity monad!
type Reader a = ReaderT Identity a
In this way, we don't need multiple abstractions to deal with "vanilla" monads and their transformers. The vanilla versions are the same as the transformers. The runReader
function can actually be written in terms of runReaderT
and runIdentity
:
runReader :: Reader r a -> r -> a
runReader action = runIdentity . (runReaderT action)
Using Identity
Now, there aren't that many reasons to use Identity
explicitly, since the monad encapsulates no computational strategy. But here's one idea. Suppose that you've written a transformation function that takes a monadic action and runs some transformations on the inner value:
transformInt :: (Monad m) => m Int -> m (Double, Int)
transformInt action = do
asDouble <- fromIntegral <$> action
tripled <- (3 *) <$> action
return (asDouble, tripled)
You would get an error if you tried to apply this to a normal unwrapped value. But by wrapping in Identity
, we can reuse this function!
>> transformInt 5
Error!
>> transformInt (Identity 5)
Identity (5.0, 15)
We can imagine the same thing with a function constraint using Functor
or Applicative
. Remember that Identity
belongs to these classes as well, since it is a Monad
!
Of course, it would be possible in this case to write a normal function that would accomplish the simple task in this example. But no matter how complex the task, we could write a version relying on the Identity
monad that will always work!
transformInt' :: Int -> (Double, Int)
transformInt' = runIdentity . transformToInt . Identity
...
>> transformInt' 5
(5.0, 15)
The Identity
monad is just a bit of trivia regarding monads. If you've been dying to learn how to really use monads in your own programming, you should sign up for our new course Making Sense of Monads! For the next week you can subscribe to our mailing list and get a discount on this course as well as our other courses!