Contact

Technologies

Functor, Applicative, and Monad usage.

2023.05.15

*In the previous article, "Understanding Functor, Applicative, and Monad" we summarized the characteristics of each, and in this article we will look at how to utilize them with specific examples. The use of Monad and other tools facilitates error handling and makes the program more resistant to specification changes.



Author : Kenta Inoue

Introduction

In the last issue, we looked at the characteristics of Functor, Applicative, and Monad. The following is a summary of them.

  • Functor m : Create m A -> m B from A -> B.
  • Applicative m : Create m A -> m B -> m C from A -> B -> C
  • Monad m : Create m A -> m B from A -> m B. (for any type A, B, C respectively)

This article will look at these applications.


Easier error handling

First, consider the following potentially error-prone functions

mdiv :: Integral a => a -> a -> Maybe a mdiv x y = if y == 0 then Nothing else Just $ x `div` y

mdiv is just a quotient operation, but the return value is of type Maybe, which returns Nothing when divided by 0.

main = do print $ 10 `mdiv` 2 -- Just 5 print $ 10 `mdiv` 0 -- Nothing

If you want to process a value that may be an error (for example, if you want to find a value that is five times this value), you cannot process it as it is, and you must separate the case by whether or not it is an error.

-- print $ 5 * (10 `mdiv` 2) -- error print $ case (10 `mdiv` 2) of Nothing -> Nothing Just x -> Just (5 * x)

However, this becomes tedious when processing increases, and also makes readability worse. So, let's rewrite it using the Functor operator <$>.

Now, in Functor Maybe, the operator <$> has type (a -> b) -> Maybe a -> Maybe b, so

-- print $ 5 * (10 `mdiv` 2) -- error print $ (5 *) <$> (10 `mdiv` 2) -- Just 25

The same process as above can be performed by writing.

Similarly, Applicative and Monad operations can be used as follows.

import GHC.Base(liftA2) main = do -- print $ (10 `mdiv` 2) * (10 `mdiv` 5) -- error print $ (*) <$> (10 `mdiv` 2) <*> (10 `mdiv` 5) -- Just 10 print $ liftA2 (*) (10 `mdiv` 2) (10 `mdiv` 5) -- Just 10 -- print $ 10 `mdiv` 2 `mdiv` 5 -- error print $ (10 `mdiv` 2) >>= (`mdiv` 5) -- Just 1 -- (<*>) :: Applcative m => m (a -> b) -> m a -> m b -- liftA2 :: Applicative m => (a -> b -> c) -> m a -> m b -> m c -- (>>=) :: Monad m => m a -> (a -> m b) -> m b

Easier to change specifications

The mdiv mentioned earlier was a function that returned the type Maybe, as we will recapitulate.

mdiv :: Integral a => a -> a -> Maybe a mdiv x y = if y == 0 then Nothing else Just $ x `div` y

However, if there are other processes that may generate errors, the Maybe type will return only Nothing when an error occurs, and it will be difficult to tell where the error occurred. Therefore, to display the error statement, the specification is changed to the Either String type as shown below instead of the Maybe type.

mdiv :: Integral a => a -> a -> Either String a mdiv x y = if y == 0 then Left "Error: divided by zero" else Right $ x `div` y

In the past, this change would require a major rewriting of the source code, but if the processing uses only operations such as Monad, as in the previous program, the program can be passed without any changes.

main = do print $ (5 *) <$> (10 `mdiv` 2) -- Right 25 print $ (*) <$> (10 `mdiv` 2) <*> (10 `mdiv` 5) -- Right 10 print $ (10 `mdiv` 2) >>= (`mdiv` 5) -- Right 1

By writing in this manner in the form of Monad, even if there are later changes to the specifications, major rewrites can be prevented as long as the processing as Monad is the same.


Applications

Consider a program that performs database processing. First, import database-related libraries.

import Control.Monad.Reader(ReaderT,liftIO) -- Library for database processing. import Database.Persistent import Database.Persistent.Sqlite import Database.Persistent.TH

Suppose we have data1::MyData of type MyData that can be stored in a certain database. Let us consider a function dbOperation::DBIO () that registers this data1 to the database and retrieves and displays the data as it is now registered. (DBIO is a certain Monad).

Since it connects to an external database, error handling is essential to the implementation of this function, but in practice it can be easily defined as follows using the Monad operation >>=.

type DBIO a = ReaderT SqlBackend IO a dbOperation :: DBIO () dbOperation = insert data1 >>= get >>= liftIO . print

We will now look at the function types used for this dbOperation.

insert :: (ommited) => val -> m (Key val) get :: (ommited) => Key val -> m (Maybe val) print :: Show a => a -> IO () class Monad m => MonadIO m where liftIO :: IO a -> m a -- (ommited) is a constraint on m and val, but omitted. -- In this case, m = ReaderT SqlBackend IO, val = MyData.

insert registers a variable of any data type val that satisfies certain constraints in the database and returns a value of type m (Key val), where Key val is the ID of the val data in the database, but may cause errors due to access to an external database, However, errors can occur due to access to external databases, and it is not always possible to return a variable of type key val. Therefore, we return m (Key val) wrapped in a certain Monad's m to handle the error. (You can think of m as Maybe or Either.) Also, get is a function that receives an ID value of type Key val, searches the database for that ID value, and returns the result as a Maybe val, but for the same reason as above, the return value is wrapped in m. The last function is print, which outputs the data, but it is liftIO to match the type. Now we can use the operation >>= in Monad m, and insert data1 >>= get >>= liftIO . print type check passes.

Thus, even functions that actually include error handling for external database connections can be written easily and independently of error handling specifications using Monad.

Incidentally, the actual error handling is done internally by the definition of Monad m, like the handling of Nothing when Monad Maybe is defined.

Supplement

above insert data1 >>= get >>= liftIO . print is the following

dbOperation :: DBIO () dbOperation = do data1Id <- insert data1 dbdata1 <- get data1Id liftIO $ print dbdata1

It is also possible to write it in a procedural language style, such as

deOperation :: DBIO () deOperation = insert data1 >>= (\data1Id -> get data1Id >>= (\dbdata1 -> liftIO (print dbdata1)))

shorthand notation. This is consistent with the original program from Monad's third axiom.


Summary

He described how to utilize Functor, Applicative, Monad, and others and their characteristics.


Back to index


Join Us

Recruit


We have an ambition like "we open up the future with technology we carry out into the world"and are established with continuing effort to become a leading company in the industry.
Because of being a young company, it will with all the employees who join the company be able to realize that we make history with all the employees who join the company.
We are currently executing hiring activity.
With Nihon Insight Technologies carrying out the technology into the world, you are expected to perform all the capability you have.


New Graduate
Mid Career


Contact Us

Contact


Contact