DEV Community

Anusorn Chaikaew
Anusorn Chaikaew

Posted on

monad in Functional programming

The term "monad" comes from category theory, a branch of mathematics that deals with the structure of mathematical objects and the relationships between them. In category theory, a monad is a way to represent a sequence of computations as a single entity, which can then be used to build more complex computations.

The term "monad" was coined by the mathematician and logician Haskell Curry, who used it to describe a way to structure functional programs in a way that makes it easier to build and reason about operations that have effects, such as reading from the filesystem or interacting with a database.

Suppose you are trying to understand a complex system's behavior. One way to do this is to build a mathematical model of the system and then use it to make predictions about how the system will behave.

A monad is like a mathematical model for a system with effects in functional programming. It provides a way to describe the system's behavior in a way that is abstracted away from the specific details of the impact itself. This allows you to reason about the system's overall behavior without getting bogged down in the details of how the effects are implemented.

For example, suppose you are building a program to interact with a database. You might use a monad to describe the behavior of the database interaction in a way independent of the specific details of how the database is implemented. This would allow you to write code that interacts with the database without having to worry about how the database is implemented.

In functional programming, monads are used in a similar way to represent sequences of computations with effects as a single entity. This allows you to build more complex programs by composing smaller monadic operations rather than having to worry about the details of how the effects are implemented.

Overall, monads are a valuable tool for structuring functional programs to make building and reason operations with effects easier. Just as a mathematical model helps a scientist understand and predict the behavior of a complex system, a monad helps a programmer understand and predict the program's behavior with effects.

Here is an example of how you might use a monad to build a program that reads a file from the filesystem and then processes the contents of the file:

First, let's define a monad for reading files:

def read_file(filename: str) -> IO[str]:
    with open(filename, 'r') as f:
        return f.read()
Enter fullscreen mode Exit fullscreen mode

The read_file function takes a filename and returns an IO monad that represents the effect of reading the file. The IO monad is a way to describe an effectful operation in a way that is abstracted away from the specific details of how the operation is implemented.

Now let's define a function that processes the contents of a file:

def process_file(contents: str) -> List[str]:
    return contents.split('\n')
Enter fullscreen mode Exit fullscreen mode

This function takes the contents of a file as input and returns a list of lines from the file.

Now we can use the read_file and process_file functions together to build a program that reads a file and processes its contents:

def read_and_process_file(filename: str) -> List[str]:
    contents = read_file(filename)
    return process_file(contents)
Enter fullscreen mode Exit fullscreen mode

another example of how you might use monads to build a program in a functional style:
Suppose you are building a program that needs to perform the following steps:

  1. Read a list of user IDs from a file
  2. Look up the details for each user in a database
  3. Filter the list of users to only include those who are over the age of 18
  4. Sort the list of users by their last name
  5. Print the list of users to the console

Here is how you might use monads to build this program:

def read_user_ids(filename: str) -> IO[List[int]]:
    with open(filename, 'r') as f:
        contents = f.read()
        user_ids = [int(x) for x in contents.split('\n')]
        return user_ids

def lookup_user(user_id: int) -> IO[User]:
    user = database.lookup(user_id)
    return user

def filter_users(users: List[User]) -> List[User]:
    return [user for user in users if user.age >= 18]

def sort_users(users: List[User]) -> List[User]:
    return sorted(users, key=lambda x: x.last_name)

def print_users(users: List[User]) -> IO[None]:
    for user in users:
        print(user)

def process_users(filename: str) -> IO[None]:
    user_ids = read_user_ids(filename)
    users = [lookup_user(user_id) for user_id in user_ids]
    users = filter_users(users)
    users = sort_users(users)
    print_users(users)

Enter fullscreen mode Exit fullscreen mode

In this example, the read_user_ids ** and **lookup_user functions both return IO monads, which represent the effects of reading from a file and interacting with a database, respectively. The other functions, filter_users, sort_users, and print_users, do not have any effects, so they do not return monads.

The process_users **function ties everything together, using the **read_user_ids **and lookup_user functions to read the list of user IDs **and look up the details for each user, and then using the filter_users, sort_users, and **print_users **functions to filter, sort, and print the list of users.

Overall, the monads in this example provide a way to describe the effects of reading from a file and interacting with a database in a way that is abstracted away from the specific details of how those effects are implemented. This makes it easier to build and reason about the overall behavior of the program without having to worry about the specifics of how the file is read or how the database is accessed.

More example:
Suppose you are working with a set of numbers and you want to perform the following operations:

Filter the set to only include even numbers
Multiply each number by 3
Take the square root of each number
Here is how you might use monads to represent these operations:

def even(x: int) -> bool:
    return x % 2 == 0

def triple(x: int) -> int:
    return x * 3

def sqrt(x: int) -> float:
    return math.sqrt(x)

def process(x: int) -> float:
    return sqrt(triple(x)) if even(x) else x

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = [process(x) for x in numbers]
print(result)

Enter fullscreen mode Exit fullscreen mode

In this example, the even, triple, and sqrt functions are all monadic functions, meaning that they return monadic values. The process function ties everything together, using the even, triple, and sqrt functions to filter, triple, and take the square root of the numbers, respectively.

Overall, the monads in this example provide a way to describe the sequence of operations being performed on the numbers in a way that is abstracted away from the specific details of how those operations are implemented. This makes it easier to build and reason about the overall behavior of the program without having to worry about the specifics of how the operations are performed.

Example Code in Haskell

import Data.List (filter)
import Data.Maybe (fromMaybe)
import Math.Sqrt (sqrt)

even :: Int -> Bool
even x = x `mod` 2 == 0

triple :: Int -> Int
triple x = x * 3

process :: Int -> Float
process x = fromMaybe (fromIntegral x) (sqrt <$> triple <$> even x)

numbers :: [Int]
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

result :: [Float]
result = map process numbers

main :: IO ()
main = print result

Enter fullscreen mode Exit fullscreen mode

In this example, the even, triple, and sqrt functions are all monadic functions, meaning that they return monadic values. The process function ties everything together, using the even, triple, and sqrt functions to filter, triple, and take the square root of the numbers, respectively.

Oldest comments (0)