Created
December 18, 2016 21:10
-
-
Save gvergnaud/95d244e8361a137a6d0b4f5ff689b7b9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ----------------------------------------- * | |
Sum | |
* ----------------------------------------- */ | |
const Sum = x => ({ | |
x, | |
concat: ({ x: y }) => Sum(x + y), | |
inspect: () => `Sum(${x})`, | |
}) | |
Sum.empty = () => Sum(0) | |
/* ----------------------------------------- * | |
Box | |
* ----------------------------------------- */ | |
const Box = x => ({ | |
fold: f => f(x), | |
map: f => Box(f(x)), | |
ap: fx => fx.map(x), | |
inspect: () => `Box(${x})`, | |
toString: () => `Box(${x})`, | |
}) | |
Box.of = x => Box(x) | |
/* ----------------------------------------- * | |
LazyBox | |
* ----------------------------------------- */ | |
const LazyBox = g => ({ | |
fold: f => f(g()), | |
map: f => LazyBox(() => f(g())), | |
ap: fx => LazyBox(() => fx.fold(g())), | |
inspect: () => `LazyBox(${g()})`, | |
toString: () => `LazyBox(${g()})`, | |
}) | |
LazyBox.of = x => LazyBox(() => x) | |
const add = x => y => x + y | |
// console.log( | |
// LazyBox(() => 2) | |
// .map(add(1)) | |
// .map(add(1)) | |
// .map(add(1)) | |
// .map(add(1)) | |
// .fold(add(2)) | |
// ) | |
/* ----------------------------------------- * | |
Data Structures | |
* ----------------------------------------- */ | |
/* ----------------------------------------- * | |
List | |
* ----------------------------------------- */ | |
const List = xs => ({ | |
xs, | |
concat: ({ xs: ys }) => List(xs.concat(ys)), | |
fold: (seed) => xs.reduce((acc, m) => acc.concat(m), seed), | |
foldMap: (mapper, seed) => xs.map(mapper).reduce((acc, m) => acc.concat(m), seed), | |
map: mapper => List(xs.map(mapper)), | |
filter: pred => List(xs.filter(pred)), | |
reduce: (reducer, seed) => xs.reduce(reducer, seed), | |
traverse: (point, traverser) => | |
xs.reduce( | |
(acc, x) => | |
acc | |
.map(xs => y => xs.concat(List.of(y))) | |
.ap(traverser(x)), | |
point(List.empty()) | |
), | |
inspect: () => `List(${xs})`, | |
toString: () => `List(${xs})`, | |
}) | |
List.empty = () => List([]) | |
List.of = (...ys) => List(ys) | |
// fold on a data structure is different from fold on a box | |
// on a datastructure fold will iterate over the values and concat | |
// them | |
// on a regular foldable container, it will be like map, but will return | |
// the raw value | |
/* ----------------------------------------- * | |
Map | |
* ----------------------------------------- */ | |
const mapValues = (mapper, obj) => | |
Object | |
.keys(obj) | |
.reduce((acc, k, i) => Object.assign({}, acc, { | |
[k]: mapper(obj[k], k, i) | |
}), {}) | |
const Map = obj => ({ | |
obj, | |
map: mapper => Map(mapValues(mapper, obj)), | |
fold: seed => Object.values(obj).reduce((acc, m) => acc.concat(m), seed), | |
foldMap: (mapper, seed) => Object.values(mapValues(mapper, obj)).reduce((acc, m) => acc.concat(m), seed), | |
}) | |
/* ----------------------------------------- * | |
foldMap | |
* ----------------------------------------- */ | |
// pareil que mapper sur la structure et la folder | |
// pratique pour reduire une list de valeurs en un Monoid | |
// on va mapper sur les valeurs pour les mettre dans un meme Monoid | |
// on va ensuite appeler fold sur la data structure, ce qui va concat | |
// tous les monoids entre eux | |
// console.log( | |
// List.of(Sum(1), Sum(2), Sum(3)) | |
// .fold(Sum.empty()) | |
// ) | |
// | |
// console.log( | |
// Map({ x: Sum(1), y: Sum(2), z: Sum(3) }) | |
// .fold(Sum.empty()) | |
// ) | |
// | |
// console.log( | |
// Map({ x: 1, y: 2, z: 3 }) | |
// .foldMap(Sum, Sum.empty()) | |
// ) | |
// | |
// console.log( | |
// List.of(1, 2, 3) | |
// .foldMap(Sum, Sum.empty()) | |
// ) | |
/* ----------------------------------------- * | |
Applicatives | |
* ----------------------------------------- */ | |
const liftA2 = (f, fx1, fx2) => fx1.map(f).ap(fx2) | |
// console.log( | |
// liftA2(x => y => `${x} - ${y}`, Box.of('lol'), Box.of('coucou')) | |
// .fold(x => x) | |
// ) | |
/* ----------------------------------------- * | |
Traversable | |
* ----------------------------------------- */ | |
// something that implements a traverse Method. | |
// apparently, it's only fora data structure | |
// let's say you have a List of Task | |
// you can convert it to a Task of List of result | |
// [Task(1), Task(2)] | |
// to | |
// Task([1, 2]) | |
// the traverse method is kinda like the map method | |
// List(1, 2, 3, 4).traverse(Task.of, x => DB.getUser(x)) | |
// => Task List User | |
// for DB.getUser :: Int -> Task User | |
// I don't really grasp the implementation of traverse yet though | |
// you can traverse a data struct ure only if it contains only applicatives functors | |
/* ----------------------------------------- * | |
possible implementtion (found it myself) | |
* ----------------------------------------- */ | |
// recursive function that return a whether a List of allValues or itself | |
// allValues => (Int, [a]) -> a -> ([a] | (a -> [a])) | |
// const allValues = (remainingArgs, values) => v => | |
// remainingArgs === 1 | |
// ? List(values.concat(v)) | |
// : allValues(remainingArgs - 1, values.concat(v)) | |
// ap :: Applicative | |
// traverseList :: ap -> (a -> ap b) -> List a -> ap List b | |
// const traverseList = (Ap, mapper) => ({ xs }) => | |
// xs.reduce( | |
// (acc, x) => acc.ap(mapper(x)), | |
// Ap(allValues(xs.length, [])) | |
// ) | |
/* ----------------------------------------- * | |
Real implementation of traverse | |
* ----------------------------------------- */ | |
const traverseList = (point, mapper) => xs => | |
xs.reduce( | |
(acc, x) => | |
acc | |
.map(xs => y => xs.concat(List([y]))) | |
.ap(mapper(x)), | |
point(List.empty()) | |
) | |
// console.log( | |
// traverseList(LazyBox.of, x => LazyBox.of(x + 1))(List.of(1, 2, 3, 4)) | |
// ) | |
// => LazyBox(List(2, 3, 4, 5)) | |
// same as | |
console.log( | |
List.of(1, 2, 3, 4) | |
.traverse(LazyBox.of, x => LazyBox.of(x + 1)) | |
) | |
// => LazyBox(List(2, 3, 4, 5)) | |
// by opposition with | |
console.log( | |
List.of(1, 2, 3, 4) | |
.map(x => LazyBox.of(x + 1)) | |
) | |
// => List(LazyBox(2), LazyBox(3), LazyBox(4), LazyBox(5)) | |
// types are flipped inside out | |
/* ----------------------------------------- * | |
Natural Transformation | |
* ----------------------------------------- */ | |
// une transformation naturel est un le fait de changer de wrapper sur notre valeur. | |
// Je peux par exemple passer d'un Either à une Task, ou d'une Maybe à une task, | |
// Ou d'une Task à une Maybe, comme on fait souvent en elm | |
// la definition serait | |
const eitherToTask = e => e.fold(Task.rejected, Task.of) | |
const taskToMaybe = t => t.fold(Nothing, Just) | |
const boxToEither = b => b.fold(Right) | |
// eitherToTask, taskToMaybe, etc, are natural transformations | |
// il faut noter que box devient une Right pour respecter les lois de la transformation naturelle | |
// pour qu'une transformation naturelle soit valid, il faut qu'elle respect | |
// quelques lois : | |
// nt = natural transformation | |
// nt(a).map(f) == nt(a.map(f)) | |
// | |
// F(a).map(f) | |
// --> | |
// F(a) ----------------> F(b) | |
// | | | |
// | | | |
// nt ↕ | | ↕ nt | |
// | | | |
// | | | |
// G(a) ----------------> G(b) | |
// --> | |
// G(a).map(f) | |
// donc si on fait la nt avant le map on doit arriver au même résultat que si | |
// l'on fait l'inverse | |
// hyper utile quand on veut join deux types differents | |
// si on a une Task(Either(x)) | |
// on peut | |
// Task(Either(x)).chain(eitherToTask) | |
// => Task(x) | |
// ce qui evite d'avoir trop de types nestés |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment