Skip to content

Instantly share code, notes, and snippets.

@bebrws
Created March 24, 2023 14:52
Show Gist options
  • Save bebrws/3bc8756359038048f025639411feb347 to your computer and use it in GitHub Desktop.
Save bebrws/3bc8756359038048f025639411feb347 to your computer and use it in GitHub Desktop.
Typescript Monad Interview Question - Filled in with answer - Idea is to ask to implement one of the functions that uses the Maybe monad
// Start with an explanation of what this code is (a Maybe monad implementation) and what a Maybe monad is and why it is useful
/*
The Maybe monad is a type of monad used in functional programming to handle values that may or may not exist or maybe invalid,
and it is particularly useful when dealing with some kind of untyped/unvalidated input. For example, a string that a user inputs.
Let's say we want the user to write a number. The string we get as input could be empty or contain letters, etc...
It is common to encounter situations where we read values that do not have a, such as when retrieving data from a database or
parsing input from a user. When a value is missing, attempting to perform operations on it can lead to errors, crashes, or unexpected behavior in the program.
The Maybe monad provides a way to handle these situations in a safe and predictable manner. It works by wrapping a
value in a container that may either hold a value or be empty. This container can then be passed around and manipulated
without the need to check if the value is present or not.
Using the Maybe monad allows us to write code that is more expressive and less error-prone. Instead of having to write
if-else statements or use other conditional constructs to check if a value is present, we can simply chain together operations
using the Maybe monad. This leads to more concise and readable code, and can also help to reduce the number of bugs and errors that can occur.
Additionally, the Maybe monad can be used to handle errors and exceptions in a more controlled and structured way. When an error occurs,
the Maybe monad can be used to propagate the error up the call stack in a predictable manner, rather than causing the program to crash or enter an inconsistent state.
Overall, the Maybe monad is a useful tool for dealing with nullable or optional values in functional programming, and can help to make code more expressive, readable, and robust.
*/
// ------------------------------------ Start of Background Code for Interview Question
type Fun<A, B> = (a: A) => B;
class Just<A> {
constructor(private readonly _value: A) { }
valueOf(): A {
return this._value;
}
}
class Nothing {
private static readonly id: unique symbol = Symbol('Nothing');
valueOf(): typeof Nothing.id {
return Nothing.id;
}
}
type Maybe<A> = Just<A> | Nothing;
/**
* fmap :: (a -> b) -> Maybe a -> Maybe b
*/
function fmap<A, B>(f: Fun<A, B>, a: Maybe<A>): Maybe<B> {
return a instanceof Nothing
? new Nothing() // fmap f Nothing = Nothing
: new Just<B>( // fmap f (Just x) = Just (f x)
f(a.valueOf())
);
}
/**
* Maybe Applicative namespace to be able to use string literal function names
*/
abstract class Applicative {
/**
* a -> Maybe a
* pure = Just
*/
static pure<A>(value: A): Maybe<A> {
return new Just<A>(value);
}
/**
* (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
*/
static '<*>'<A, B>(f: Maybe<Fun<A, B>>, a: Maybe<A>): Maybe<B> {
return f instanceof Nothing
? new Nothing()
: fmap<A, B>(f.valueOf(), a);
}
/**
* (<$>) :: (Functor f) => (a -> b) -> Maybe a -> Maybe b
* (<$>) = fmap
*/
static '<$>'<A, B>(f: Fun<A, B>, a: Maybe<A>): Maybe<B> {
return fmap<A, B>(f, a);
}
}
/**
* Maybe Monad namespace to be able to use string literal function names
*/
abstract class Monad {
/**
* return :: a -> Maybe a
* return = pure
*/
static return<A>(value: A): Maybe<A> {
return Applicative.pure<A>(value);
}
/**
* (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
*/
static '>>='<A, B>(a: Maybe<A>, f: Fun<A, Maybe<B>>): Maybe<B> {
return a instanceof Nothing
? new Nothing()
: f(a.valueOf());
}
}
/**
* Maybe Monad instance to use in OOP
*/
class MonadInst<A> {
constructor(private readonly _value: Maybe<A>) { }
bind<B>(f: Fun<A, Maybe<B>>): MonadInst<B> {
return new MonadInst<B>(
Monad['>>='](this._value, f)
);
}
toMaybe(): Maybe<A> {
return this._value;
}
}
// ------------------------------------ Start of Interview Question
const userInputMonadInst: MonadInst<string> = new MonadInst(new Just("4"));
function parseUserInput(userInput: string): Maybe<number> {
const parsedInt = parseInt(userInput, 10);
// If the user didn't provide valid input we cannot continue and provide a valid response
if (isNaN(parsedInt)) { return new Nothing(); }
return new Just(parsedInt);
}
function twoDividedByUserInputIfNonZero(userInputNumber: number): Maybe<number> {
if (userInputNumber === 0) {
// Dividing by 0 doesn't make sense
return new Nothing();
} else {
// We can provide a valid result!
return new Just(2 / userInputNumber);
}
}
const result: Maybe<number> = userInputMonadInst.bind(parseUserInput).bind(twoDividedByUserInputIfNonZero).toMaybe();
if (result instanceof Nothing) {
console.error("There was an issue.")
} else {
console.log("Result: ", result.valueOf())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment