Skip to content

Instantly share code, notes, and snippets.

@gvergnaud
Created December 18, 2016 21:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gvergnaud/95d244e8361a137a6d0b4f5ff689b7b9 to your computer and use it in GitHub Desktop.
Save gvergnaud/95d244e8361a137a6d0b4f5ff689b7b9 to your computer and use it in GitHub Desktop.
/* ----------------------------------------- *
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