Andy Hunt
(And can they be safe?)
Wikipedia
Wikipedia
const a = null;
a.prop; // #1
const b = undefined;
b.prop; // #2
What happens at 1 and 2?
> typeof null
'object'
Null, undefined or false?
const a = { a: { b: { c: [1, 2, 3] } } };
const b = { a: { b: { c: undefined } } };
const sumC = data => {
// what goes in here?
};
const a = { a: { b: { c: [1, 2, 3] } } };
const b = { a: { b: { c: undefined } } };
const add = (a, b) => a + b;
const sumC = data => data.a.b.c.reduce(add, 0);
sumC(a); // 6
sumC(b); // ?
TypeError: Cannot read property 'reduce' of null
const a = { a: { b: { c: [1, 2, 3] } } };
const b = { a: { b: { c: undefined } } };
const add = (a, b) => a + b;
const sumC = data => {
if (data.a.b.c && Array.isArray(data.a.b.c)) {
return data.a.b.c.reduce(add, 0);
}
// What do we return here?
}
// Option 1
return false;
// Option 2
return null;
// Option 3
throw new Error("An array is required to sum");
// Option 4
throw new CannotSumError("C should be an array");
Crocks
data Bool = True | False
User defined types
type EventId = Int
Type aliases / Synonyms
add :: Int -> Int -> Int
Function signatures
data Maybe a = Just a | Nothing
Maybe is well suited for capturing disjunction when the cause of the "error" case does not need to be communicated. For example, providing default values on specific conditions.
const a = { a: { b: { c: [1, 2, 3] } } };
const b = { a: { b: { c: undefined } } };
const add = (a, b) => a + b;
const sumC = data => {
if (data.a.b.c && Array.isArray(data.a.b.c)) {
return Just(data.a.b.c.reduce(add, 0));
}
return Nothing();
}
const prop = require("crocks/Maybe/prop");
const propPath = require("crocks/Maybe/propPath");
const data = { a: { b: { c: [1, 1, 2, 3, 5] } } };
const a = prop("a", data); // Just { b: { c: [1, 1, 2, 3, 5] } }
const b = prop("b", data); // Nothing
const c = propPath(["a", "b", "c"], data); // Just [1, 1, 2, 3, 5]
const d = propPath(["a", "b", "d"], data); // Nothing
A value which has a functor must provide a map method. The map method takes one argument
map :: Functor f => f a ~> (a -> b) -> f b
Just.prototype.map = function(fn) { return Just(fn(this.value)) };
Nothing.prototype.map = function(fn) { return this; };
const a = { a: { b: { c: [1, 2, 3] } } };
const b = { a: { b: { c: undefined } } };
const sumC = data =>
propPath(["a", "b", "c"], data)
.map(vals => vals.reduce(add, 0));
sumC(a); // Just 6
sumC(b); // Nothing
propPath :: Foldable f => f (String | Integer) -> a -> Maybe b
Arrows indicate currying
Value is only returned when all other arguments are provided
"f" has to be an ADT which implements the foldable typeclass (i.e. an array or List)
propPath :: Foldable f => f (String | Integer) -> a -> Maybe b
map :: (a -> b) -> m a -> m b
reduce :: (b -> a -> b) -> b -> m a -> b
compose :: ((y -> z), ..., (a -> b)) -> a -> z
pipe :: ((a -> b), ..., (y -> z)) -> a -> z
// getC :: a -> Maybe b
const getC = propPath(["a", "b", "c"]);
// sum :: Foldable f => f Number -> Number
const sum = reduce(add, 0);
// sumC :: a -> Maybe Number
const sumC = pipe(getC, map(sum));
const sum = reduce(add, 0);
"empty" value (aka identity)
Combines two values together (aka concat)
any ADT that provides both an empty and a concat function can be used as a Monoid
Each Monoid provides a means to represent a binary operation and is usually locked down to a specific type. These are great when you need to combine a list of values down to one value.
// Instead of this
const add = (a, b) => a + b;
const sum1 = reduce(add, 0);
// We can use the built in behaviours of the Sum Monoid
const sum2 = mreduce(Sum);
π‘Note that sum1 and sum2 are equivalent - both take an array of numbers and return a number
// sumC :: a -> Maybe Number
const sumC = pipe(
propPath(["a", "b", "c"]),
map(mreduce(Sum));
Our program has been completely built from generic, reusable library code
mreduce
vs mconcat
vs mreduceMap
vs mconcatMap
mconcat :: Monoid m, Foldable f => m -> f a -> m a
mconcatMap :: Monoid m, Foldable f => m -> (b -> a) -> f b -> m a
mreduce :: Monoid m, Foldable f => m -> f a -> a
mreduceMap :: Monoid m, Foldable f => m -> (b -> a) -> f b -> a
mreduce
vs mconcat
vs mreduceMap
vs mconcatMap
Jumping back...
// head :: Foldable f => f a -> Maybe a
const head = xs => (Array.isArray(xs) && xs.length > 0 ? Just(xs[0]) : Nothing();)
// head :: Foldable f => f a -> Maybe a
function head(xs) {
if (Array.isArray(xs) && xs.length > 0) {
return Just(xs[0]);
}
return Nothing();
}
Nothing
?
Maybe
is just the start of this adventure...
data Either e a = Left e | Right a
data Result e a = Err e | Ok a
data Async e a = Rejected e | Resolved a
data RemoteData e a = NotAsked | Loading | Error e | Success a
type Pair a b = (a, b)
The specifications in this list do not derive from goals such as trying to write rules for lists and maps. Instead, they start by noticing rules that apply in common to disparate structures.
Yeah this is really not happening. It totally ignores reality in favor of typed-language fantasy land, making a more awkward and less useful API just to satisfy some peoples' aesthetic preferences that aren't even applicable to JavaScript.