Armin Taheri
Contents
double mysqrt(int n, double x) {
double x_last = 1.0;
double f = x_last * x_last - x;
double df = (2 * x_last);
for (int i = 0; i < n; i++) {
x_last = x_last - f / df ;
f = x_last * x_last - x;
df = 2 * x_last;
}
return x_last;
}
sqrt(1000, 5) // 2.236068
mysqrt 0 x = 1.0
mysqrt n x = x_last - f / df
where
x_last = mysqrt (n-1) x
f = x_last * x_last - x
df = 2 * x_last
mysqrt 1000 5 -- 2.23606797749979
double mysqrt(int n, double x) {
First assign 1.0 to x_last
Then compute x_last*x_last - x and store it into f
Then compute 2 * x_last and store it into df
then for n iterations do:
compute x_last - f / df and store it into x_last
return x_last
}
You declare what your function maps its arguments to and nothing more.
Pros:
Pros:
Cons:
Cons:
class Stack {
constructor() {
this.stack = []
}
push(a) {
this.stack[this.stack.length] = a;
}
pop() {
var out = this.stack[this.stack.length-1];
delete this.stack[this.stack.length-1];
this.stack.length -= 1;
return out;
}
}
// We now have to test
// many possible execution paths
var s = new Stack();
s.push(3)
s.pop() // 3
s.pop() // Error!
// General
var copyArray = function (array) {
return array.slice(0, array.length);
}
var append = function (value, array) {
var newArr = copyArray(array);
newArr[newArr.length] = value;
return newArr
}
var removeAt = function (n, array) {
var newArr = copyArray(array);
newArr.splice(n, 1);
return [array[n], newArr];
}
// Stack
var push = append;
var pop = function (array) {
return removeAt(array.length-1, array);
}
// Queue
var queue = append;
var dequeue = function (array) {
return removeAt(0, array);
}
4.0 * 4.0 :: Double
mysqrt :: Double -> Double
toUpper :: String -> String
strLength :: [Char] -> Int
(+) :: Number -> Number -> Number
We either have values or functions. Value types are denoted as:
expression :: Type
Function types are denotes as
expression :: Type -> ... -> Type
identity :: a -> a
identity x = x
Functions do not need to have a concrete type for its arguments. Think of the identity function:
The type 'a' could be anything as long as we are consistent whenever 'a' shows up in the type.
When a function f :: a -> b is applied to an expression e :: a,
we get an expression (f e) :: b
When a function f :: a -> b
and a function g :: b -> c
are composed, we get a function
(f . g) :: a -> c
Composition is how pipelines are created in functional programs.
With these laws it does not take more than knowing the type of a function to know how to apply and compose it in a larger abstraction.
Often we can debug a dynamically typed programming language just by logically applying the application and composition inference rules for function types.
add :: (Number, Number) -> Number
add (1, 1) :: Number
(addCurry) :: Number -> (Number -> Number)
(addCurry 1) :: Number -> Number
((addCurry 1) 1) :: Number
Functions are typically seen as taking a tuple (x1, x2, ..., xn) and mapping to a result.
These functions can be "curried" so that they can be partially applied. We can see this as "configuring" a function before applying it to it's final argument.
Type classes allow us to add assumptions to the type of a function without restricting the actual type of the function.
It is good practice to create data structures that conform to one or more type classes so libraries can be written more generally.
Javascript does not have type classes but we can still keep them in mind when coding. Examples are:
Eq a
-- the type 'a' must have a way to equate two values of type 'a'.
Ord a
-- the type 'a' must have a way to order elements of 'a'.
Num a
-- the type 'a' must have arithmetic operations defined.
We can now add assumtions about the inputs of our function:
addNums :: Num a => a -> a -> a
sort :: Ord a => [a] -> [a]
In functional programming we have more abstract type classes which allow us to write more modular functions.
One of these is the Functor typeclass. A functor instance wraps another type inside a context.
map :: Functor f => (a -> b) -> f a -> f b
map(identity, list) === list
map(f, map(g, list)) === map((f . g), list)
-- Examples:
data List a = Cons a (List a) | Nil
-- Cons 1 (Cons 2 (Cons 3 (Nil)))
instance Functor List where
map f Nil = Nil
map f Cons x xs = Cons (f x) (map f xs)
data Maybe a = Just a | Nothing
instance Functor Maybe where
map f Nothing = Nothing
map f (Just x) = Just (f x)
We will implement a form in javascript using the functional programming approach.
We start by defining the Maybe Functor
var Just = function (value) {
return {
value: value,
map: function (f) {
return Just(f(value));
}
};
};
var Nothing = {
map: function() {
return Nothing;
}
};
We then define a general map function for all functors
// map: Functor f => (a -> b) -> f a -> f b
var map = function (f){
return function (m) {
return m.map(f);
}
};
Now we define a function that checks of a string's first letter is capitalized and return Just(string) if it's valid and Nothing if it's invalid
// nameValidation: String -> Maybe String
var nameValidation = function (s) {
if (s.match('[A-Z]\w*') === null) {
return Nothing;
}
return Just(s);
};
Our Side-Effecting code are seperated into their own functions
// print: String -> Effect ()
var print = function (message) {
document.querySelector('#log').innerHTML += '<li>' + message + '</li>';
};
// clearLog: () -> Effect ()
var clearLog = function () {
document.querySelector('#log').innerHTML = '';
};
Our main form code runs in the submit function. It attemps to validate the name and map the print function on the ouput. If the output is Nothing then the mapped function is not called.
// submit: Maybe (Effect ())
function submit () {
var vname = nameValidation(document.querySelector('.name-input').value);
clearLog();
map(print)(vname);
}
We now have a validated input field.
Often we would like to validate inputs and combine them in a function that takes many arguments. Applicative functors are used to wrap these functions when we want to apply them on wrapped values.
ap :: Applicative m => m (a -> b) -> m a -> m b
of :: Applicative m => a -> m a
ap(of(identity), m) === m
ap(of(f), of(x)) = of(f(x))
-- Example:
data Maybe a = Just a | Nothing
instance Applicative Maybe where
of x = Just x
ap Nothing any = Nothing
ap any Nothing = Nothing
ap (Just f) (Just x) = Just (f x)
-- ap(of(+), (Just 1)) === (Just 3)
-- ap(of(+), Nothing) === Nothing
Let's try to validate emails too.
We extend the definition of Just and Nothing to include the 'ap' function
var Just = function (value) {
return {
type: 'JUST',
value: value,
...
ap: function (m) {
return m.map(value);
}
};
};
var Nothing = {
type: 'NOTHING',
...
ap: function() {
return Nothing
}
};
We define a general 'ap' function for Applicative Functors.
// ap: Applicative f => f (a -> b) -> f a -> f b
var ap = function (m1) {
return function (m2) {
return m1.ap(m2);
}
};
// ES6 arrow functions:
const ap = m1 => m2 => m1.ap(m2);
We generalize validation:
// validate: Regex -> String -> Maybe String
var validate = function(r) {
return function (s) {
if (s.match(r) === null) {
return Nothing;
}
return Just(s);
}
}
// nameValidation: String -> Maybe String
var nameValidation = validate(/[A-Z]\w*/);
// emailValidation: String -> Maybe String
var emailValidation = validate(/\w+\@\w+/);
// ES6:
const validate = r => s => {
if (s.match(r) === null) {
return Nothing;
}
return Just(s);
}
And we have our input combiner:
// userToString: String -> String -> String
var userToString = function (name) {
return function (email) {
return '[Name: ' + name + ', Email: ' + email + ']';
};
};
// ES6:
const userToString = name => email =>
'[Name: ' + name + ', Email: ' + email + ']';
Notice in our types: We don't expect Maybe String
Our function does not assume anything about its input except that it's a String.
We need to upgrade our combiner to type:
Maybe String -> Maybe String -> Maybe String
// lift: Applicative f => (a -> b -> c) -> (f a -> f b -> f c)
var lift = function (op) {
return function (m1) {
return function (m2) {
return ap(map(op)(m1))(m2);
}
}
};
// ES6:
const lift = op => m1 => m2 => ap(map(op)(m1))(m2);
'lift' is a general transformation from binary operators to Applicative operators
We can then print our wrapped user and email using the map function
var userString = lift(userToString)(vname)(vemail);
map(print)(userString);
Now we can combine validated input fields!
We can use a more descriptive Applicative called Validated:
var Success = function (value) {
return {
type: 'SUCCESS',
value: value,
map: function (f) {
return Success(f(value));
},
ap: function (m) {
return m.map(value);
}
};
};
var Failure = function (errors) {
return {
type: 'FAILURE',
value: errors,
map: function() {
return Failure(errors);
},
ap: function(m) {
switch (m.type) {
case 'SUCCESS':
return Failure(errors);
case 'FAILURE':
return Failure(errors.concat(m.value));
}
}
};
};
We can now return error values from our validation functions.
// validate: (Regex, [String]) -> String -> Validated String
var validate = function(r, errors) {
return function (s) {
if (s.match(r) === null) {
return Failure(errors);
}
return Success(s);
};
};
// nameValidation: String -> Validated String
var nameValidation = validate(/[A-Z]\w*/, ['Name must begin capitalized']);
// emailValidation: String -> Validated String
var emailValidation = validate(/\w+\@\w+/, ['Email must have form XXX@XXX']);
The Validated implementation for 'ap' handles error accumulation for us to log later:
// printValidated: Validated String -> Effect ()
var printValidated = function (validated) {
switch (validated.type) {
case 'SUCCESS':
return print(validated.value);
case 'FAILURE':
return validated.value.forEach(print);
}
};
var userString = lift(userToString)(vname)(vemail);
printValidated(userString);
And our errors will now log!
We often need to call API's from systems that may fail. The Monad typeclass can model these failures in a datatype and sequence operations that may fail.
chain :: Monad m => m a -> (a -> m b) -> m b
of :: Monad m => a -> m a
chain(of(x), f) === f(x)
chain(m, of) === m
-- join flattens nested monads
join :: Monad m => m (m a) => m a
-- Example:
data Maybe a = Just a | Nothing
instance Applicative Maybe where
of x = Just x
chain Nothing any = Nothing
chain (Just x) f = f x
join m = chain m identity
We extend the Validated typeclass to allow chaining:
var Success = function (value) {
return {
type: 'SUCCESS',
value: value,
...
chain: function (f) {
return f(value);
}
};
};
var Failure = function (errors) {
return {
type: 'FAILURE',
value: errors,
...
chain: function () {
return Failure(errors);
}
};
};
We wrap our DBMS API with functions that handle failure using the Validated monad.
var getUsersDB = function (db) {
try {
return Success(db.getAll());
} catch (e) {
return Failure([e.message]);
}
};
// createUserDB: DB -> String -> Validated Number
var createUserDB = function (db) {
return function (name) {
try {
return Success(db.addUser(name));
} catch (e) {
return Failure([e.message])
}
};
};
We wrap our DBMS API with functions that handle failure using the Validated monad.
// setEmailDB: DB -> String -> Number -> Validated Number
var setEmailDB = function (db) {
return function (email) {
return function (id) {
try {
return Success(db.setEmail(id, email));
} catch (e) {
return Failure([e.message]);
}
};
};
};
We have our combining function that takes name and email and creates a user in the database:
// newUserDB: DB -> String -> String -> Validated Number
var newUserDB = function (db) {
return function (name) {
return function (email) {
return createUserDB(db)(name).chain(setEmailDB(db)(email));
// Or equivelently
// createUserDB(db)(name).chain(function (id) {
// return setEmailDB(db)(email)(id);
// })
//
};
};
};
Notice the type: We don't assume the input is wrapped in Validated. We need to lift the newUserDB function.
lift(newUserDB(db))(vname)(vemail) :: Validated (Validated [String])
join(lift(newUserDB(db))(vname)(vemail)) :: Validated [String]
But this means if we lift newUserDB we will get a nested monad:
'join' will flatten the monad so that if input validation fails, we see those errors, otherwise we'll see errors if the DB fails. If all goes well the DB is updated.
// userToString: User -> String
var userToString = function (user) {
return '[ Name: ' + user.name + ', Email:' + user.email + ' ]'
};
// printUsers: Validated [User] -> Effect()
var printUsers = function (users) {
switch(users.type) {
case 'SUCCESS':
var userStrings = map(userToString)(users.value)
return map(print)(userStrings);
case 'FAILURE':
return users.value.map(print);
}
};
We now need a way to print the users in the database or the errors that accumulate.
// db: DB
var db = new Database();
// submit: Validated (Effect ())
function submit() {
var vname = nameValidation(document.querySelector('.name-input').value);
var vemail = emailValidation(document.querySelector('.email-input').value);
var users =
join(lift(newUserDB(db))(vname)(vemail))
.chain(function() { return getUsersDB(db); });
clearLog();
printUsers(users);
}
We can initialize our database and wait for input:
We now have a form that saves to the database and shows errors: