var Container = function(x) {
this.__value = x;
}
Container.of = function(x) { return new Container(x); };
- Container is an object with one property. Lots of containers just hold one thing, though they aren't limited to one. We've arbitrarily named its property __value.
- The __value cannot be one specific type or our Container would hardly live up to the name.
- Once data goes into the Container it stays there. We could get it out by using .__value, but that would defeat the purpose.
// (a -> b) -> Container a -> Container b
Container.prototype.map = function(f) {
return Container.of(f(this.__value));
}
- We can work with __value from inside of the container.
- Secure our value & can work with different data types && handle null....
- Container
- is fairly boring.
- has about the same impact as our id function(again there is a mathematical connection we'll look at when the time is right). However, there are other functors that have a proper map function which can provide useful behaviour whilst mapping.
Maybe
functors enforce null checks.- don't have to write if(null === undefined) every where --> define it once and let it do it's thing.
- Enforcing null check with
Maybe
:- Use a function like
safeHead
which checks for the existence of array and supplies our function with that intel - Intentionally cause null
- Use a function like
// withdraw :: Number -> Account -> `Maybe`(Account)
var withdraw = curry(function(amount, account) {
return account.balance >= amount ?
`Maybe`.of({
balance: account.balance - amount,
}) :
`Maybe`.of(null);
});
// finishTransaction :: Account -> String
var finishTransaction = compose(remainingBalance, updateLedger); // <- these composed functions are hypothetical, not implemented here...
// getTwenty :: Account -> `Maybe`(String)
var getTwenty = compose(map(finishTransaction), withdraw(20));
getTwenty({
balance: 200.00,
});
// `Maybe`("Your balance is $180.00")
getTwenty({
balance: 10.00,
});
// `Maybe`(null)
// safeHead :: [a] -> `Maybe`(a)
var safeHead = function(xs) {
return `Maybe`.of(xs[0]);
};
var streetName = compose(map(_.prop('street')), safeHead, _.prop('addresses'));
streetName({
addresses: [],
});
// `Maybe`(null)
streetName({
addresses: [{
street: 'Shady Ln.',
number: 4201,
}],
});
// `Maybe`("Shady Ln.")
- Writing unsafe software is like taking care to paint each egg with pastels before hurling it into traffic; like building a retirement home with materials warned against by three little pig.
- Characteristics of a true null checking monday
- split in 2 types:
- 1 - checks for a something (value
- 1 - checkss for nothing
Some(x)
/None
orJust(x)
/Nothing
are often used instead of aMaybe
- split in 2 types:
Either
*throw ... catch
is not pure * when an error is thrown, it throws a fit of 0s and 1s against the input.- Instead of blowing a binary load, we can use
Either
- Instead of blowing a binary load, we can use
- Functors are great for lots of reasons. Most importantly, they’re an abstraction that you can use to implement lots of useful things in a way that works with any data type.
- For instance, what if you want to kick off a chain of operations, but only if the value inside the functor is not undefined or null?
-
Monads are a way to compose functions that require context in addition to the return value.
- I/O, branching, computation
-
Moads type lift, flatten, and map so that types line up for lifting functions
a => M(b)
-
"A mapping from some type
a
to some typeb
along with some computational context (hidden within the implementaton oflift
map
andflatten
) -
Functions Map, Functors map with context, Monads flatten AND map with context.
map
- apply a fn toa
and returnb
(a => b
)context
- computation detail of monad's composition (lift
,flatten
,map
).- allows us to compose monads
- Mapping inside the context means that you apply a function from a => b to the value inside the context, and return a new value b wrapped inside the same kind of context
- typelift - lift a type into a context so we can compute with that value.
- flatten - unwrap the value
F(a) => a
-
A monad is a type of functor
-
if you want to compose functions from
a => F(b), b => F(c)
, and so on, you need monads. Let's swap theF()
forM()
to make that clear: -
For synchronous, eager function applications over array data, this is overkill. However, lots of things are asynchronous or lazy, and lots of functions need to handle messy things like branching for exceptions or empty values.
Performance Warning: I’m not recommending this for arrays. Composing functions in this way would require multiple iterations over the entire array (which could contain hundreds of thousands of items). For maps over an array, compose simple a -> b functions first, then map over the array once, or optimize iterations with .reduce() or a transducer.
- .then() behaves differently & does not strictly obey all the mathematical laws that all functors and/or monads must satisfy for all given values.
- Promises expect a
then
.- They are different from Monads because they don't type lift. Instead, the expect a promise.
- if
then
is calledchain
it wont call it right away, it will wait, then retrieve.
# Composing with functions
g: a => b
f: b => c
h = f(g(a)): a => c
# Composing with functors
g: F(a) => F(b)
f: F(b) => F(c)
h = f(g(Fa)): F(a) => F(c)
# Composing with Monads
g: a => M(b)
f: b => M(c)
h = composeM(f, g): a => M(c)
const composeM = method => (...ms) => (
ms.reduce((f, g) => x => g(x)[method](f))
);