Monads

References:

Monads

Monad: a software design pattern.

In functional programming, a monad is a software design pattern with a structure that combines program fragments (functions) and wraps their return values in a type with additional computation.

General purpose languages use monads to reduce [boilerplate code] needed for common operations (such as dealing undefined values of fallible functions, or encapsulating bookkeeping code.)

Functional languages use monads to turn complicated sequences of functions into succint pipelines that abstract away control flow, and side-effects.

Boilerplate code: In computer programming, boilerplate code, or simply boilerplate, are sections of code that are repeated in multiple places with little to no variation. When using languages that are considered verbose, the programmer must write a lot of boilerplate code to accomplish only minior functionality.

  • All about monads: https://wiki.haskell.org/All_About_Monads

    • Monads is a way to structure computations in terms of values and sequences of computations using typed values.
    • Monads are strategies for solving coding problems that recur often, regardless of what you are writing.
    • More than this, monads help make strategies composable: Combining computations into more complex computations.
    • Examples: Maybe type, the List, and I/O are monads (in Haskell).
      • List solves a common problem: you need a very basic collection of items of the same type, with some easy-to-understand behavior and performance characteristics.
      • Maybe type: rescues you from having to write lots of null pointer checks – or else debug code that does not have enough of them.
      • I/O makes it possible to interact with a program at all.
  • a monadic function is a function with a single argument, written to its right. cite

  • A short and simple answer: a monadic function is one whose return data signature/type is the same as its input data signature/type. cite

  • MonadComputation Builder: monad is a pattern for chaining operations. It looks a bit like method chaining in object-oriented languages, but the mechanism is slightly different. The pattern can be used in any language which support higher-order functions (that is, functions which can take other functions as arguments). cite

  • For those comming from language where the semicolon is a statement separator in imperative control flow, the metaphor of “Programmable Semicolon” has helped many understand the advantages of monads. The monad determines how combined computations form a new computation and frees the programmer from having to code the combination manually each time it is required. Think of monads as “statically typed filters” (in the Unix sense of “pipes and filters”) and you may be halfway there.

Origin

Both the concept of a monad and the term originally come from category theory, where a monad is defined as a [functor] with additional structure.

functor: In functional programm, a functor is a design pattern inspired by the definition from category theory that allows one to apply a function inside a generic type without changing the structure of the generic type.

Usage Scenarios

More exactly, a monad can be used where unrestricted access to a value is inappropriate for reasons specific to the scenario.

In case of Maybe monad, it is because the value may not exist.

In case of IO monad, it is because the value may not be known yet, such as when the monad represents user input that will only be provided after a prompt is displayed.

In all cases the scenarios ???

In Haskell I/O

In Haskell, monads play a central role in the I/O system. It is not essential to understand monads to do I/O in the Haskell, but understanding the I/O monad will improve your code and extend your capabilities.

Three Properties that are useful

  • Modularity. They allow computations to be composed from simpler computations and separate the combination strategy from the actual compuations being performed.
  • Flexibility. They allow functional programs to be much more adaptable than equivalent programs written without monads. This is because the monad distills the computational strategy into a single place instead of requiring it be distributed throughout the entire program.
  • Isolation. They can be used to create imperative-style computational structures which remain safely isolated from the main body of the functional program. This is useful for incorporating side-effects (such as I/O) and state (which violates referential transparency) into a pure functional language like Haskell.

Monad Components

Monadic type + Unit operation + Bind operation (Monadic functions).

Monadic Type: A type (Maybe)

Unit Operation: A type converter (Just (x))

Bind Operation: A combinator for monadic functions (>>= or .map())

Example: the Maybe type.

Example in Rust

//
enum Maybe<T> {
    Just(T),
    Nothing,
}

// return nothing when the function fails.
fn divide(x: Decimal, y: Decimal) -> Maybe<Decimal> {
    if y == 0 { return Nothing }
    else { return Just(x / y) }
}
// divide(1.0, 4.0) -> returns Just(0.25)
// divide(3.0, 0.0) -> returns Nothing


// use if to test whether Maybe has a value.
let m_x = divide(3.14, 0.0); // see divide function above
// The if statement extracts x from m_x if m_x is the Just variant of Maybe
if let Just(x) = m_x {
    print!("answer: ", x)
} else {
    print!("division failed, divide by zero error...")
}

// use pattern matching 
let result = divide(3.0, 2.0);
match result {
    Just(x) => print!("answer: ", x),
    Nothing => print!("division failed, we'll get em next time."),
}

// Use Monads to compose functions that returns `Maybe` together:
fn chainable_division(maybe_x: Maybe<Decimal>, maybe_y: Maybe<Decimal>) -> Maybe<Decimal> {
    match (maybe_x, maybe_y) {
        (Just(x), Just(y)) => { // If both inputs are Just, check for division by zero and divide accordingly
            if y == 0 { return Nothing }
            else { return Just(x / y) }
        },
        _ => return Nothing // Otherwise return Nothing
    }
}
chainable_division(chainable_division(Just(2.0), Just(0.0)), Just(1.0)); // inside chainable_division fails, outside chainable_division returns Nothing

// Use a `bind` operator (also known as `map`, `flatmap`, or `shove`), to remove a lot of boilerplate (all those `Just` expressions)
// Rust example using ".map". maybe_x is passed through 2 functions that return Maybe<Decimal> and Maybe<String> respectively.
// As with normal function composition the inputs and outputs of functions feeding into each other should match wrapped types. (i.e. the add_one function should return a Maybe<Decimal> which then can be unwrapped to a Decimal for the decimal_to_string function)
let maybe_x: Maybe<Decimal> = Just(1.0)
let maybe_result = maybe_x.map(|x| add_one(x)).map(|x| decimal_to_string(x))

Haskell Type Constructors

-- Maybe is a type constructor; Nothing and Just are data constructors.
data Maybe a = Nothing | Just a

-- Just is a data constructor. You can construct a data value by applying the Just data constructor to a value:
country = Just "China"

-- Maybe is a type constructor. You can construct a type by applying the Maybe type constructor to a type:
lookupAage :: DB -> String -> Maybe Int

Type Constructors – Type Polymorphic – Type Container:

Polymorphic types are like containers that are capable of holding values of many different types. So Maybe Int can be thought of as a Maybe container holding an Int value (or Nothing) and Maybe String would be a Maybe container holding a String value (or Nothing).

Make Type of Type Containers Polymorphic:

Use m a to represent a container of some type (m) holding a value of some type (a) !

Example in Haskell

halve :: Int -> Maybe Int
halve x
  | even x = Just (x `div` 2)
  | odd x  = Nothing

 -- This code halves x twice. it evaluates to Nothing if x is not a multiple of 4
halve x >>= halve


-- ??? 
chainable_division(mx,my) =   mx >>=  ( λx ->   my >>= (λy -> Just (x / y))   )

Haskell do notation

A pattern that used many times – abstract out as monad

m >>= f  = case m of
        Nothing -> Nothing
        Just x -> f x
eval :: Expr -> Maybe Int
eval (Val n) = return n
eval (Div x y) = do n <- eval x
                    m <- eval y
                    safediv n m
                 

The Maybe monad:

return :: a -> Maybe a  
>>= :: Maybe a -> (a -> Maybe b) -> Maybe b

Points:

  • work with other effects (not only failures, but also I/Os, )
  • pure programming with effects;
  • explicit usage of effects in types;
  • any effects: effect polymorphism.

Haskell IO

Reference: Haskell for Imperative Programmers #15 – IO IO actions.

input might be zero. Output something.


??? -----> IO ----> ()
           |
          \|/
           environment

Monadic Function

Monadic type + Monadic functions.

Monadic type.

Monadic function:

More

Created Dec 14, 2022 // Last Updated Jan 3, 2023

If you could revise
the fundmental principles of
computer system design
to improve security...

... what would you change?