Functional Architecture Patterns

In notebook:
Work Notes
Created at:
2020-01-26
Updated:
2020-02-08
Tags:
Functional Programming JavaScript

Functional Architecture Patterns

By Brian Lonsdorf on FrontendMasters @drboolean

The paradox is that architecture starts to break down when the app grows big enough, but in a one day course, it's hard to go that far.

Architecture is subjectiver, can have different goals:, modular, extendable, performance, maintenance, readabality etc. You must chose your north star.

Concept of grouping and naming procedures and grouping them into clases or modules. Same for data. This leads to lot of names.

Domain Driven Design by Eric Evans. A great book.

It's very hard to come up with good names, this book gives good strategy for it.

You need to know the domain when you start coding. And a lot of times you come with names like converter or processors.

05:04 - Functional Architecture Patterns on Livestream Let's take a step back. Procedures. We're going to compose the procedures. But you need to laws so that you can compose with confidence. To have confindence in idempondence, side effects, order of execution.

06:56 - Functional Architecture Patterns on Livestream Shows some laws, associative, communicatevie, identity, distributive

06:56 - Functional Architecture Patterns on Livestream Shows some laws, associative, communicatevie, identity, distributive.

Functions with defined contracts

You map the contracts to mathematical concepts.

Shows example of User class.

07:52 - Functional Architecture Patterns on Livestream But now, to get the full name, we create joinwithSpace that has all the laws, e.g. associativity so you can combine it in different groups and it will give the same result.

08:24 - Functional Architecture Patterns on Livestream

so joinwithSpace is a reusable utility. He introduces the concept of joinable. Any type that has .join, e.g. an array. Now joinwithSpace

  joinwithSpace = joinable => joinable.join(' ')
joinwithSpace([user.firstName, user.lastName])

will program to the interface joinable. This is the same thing as encapsulation. joinable can only do one thing and doesn’t know anything else, unlike the OO user object.

10:18 - Functional Architecture Patterns on Livestream The identity as abstraction, it can only do one thing.

does in runjs

  const {Either} = require('types')

const identity = a => a
Either.of(2).fold(identity, identity)

This is how he gets the value out. So he doesn't need to write a handler to get the value out. first it's a bit weird to see. So identity is used in a lot of places in the functors and monads.

13:09 - Functional Architecture Patterns on Livestream

Highly generalized functions - the guiding principle, to go down and abstract out to the smallest possible piece.

13:41 - Functional Architecture Patterns on Livestream Composition slide Benefits:

  • infinite use cases
  • simple easy to understand pices
  • reuse

Drawbacks:

  • harder to change the implementation
  • harder for user to compose

Abstract algebra: you have several pieces, but they all obey some laws so you can combine them with confidence (so solves the drawbacks)

15:37 - Functional Architecture Patterns on Livestream The other side where you have just a black box. Flexibility in implementation, but less use cases to support. And sooner or later you will be passing in flags or ifelse statements, because you don't (can't) satisfy all the use cases. So the conditions get pushed into the "box".

We will focus on compostions.

17:17 - Functional Architecture Patterns on Livestream

Definition of group slide. (Screenshot) This is how our documentation could look like.

favor composable functions, mostly

mostly!

18:48 - Functional Architecture Patterns on Livestream Shapes slide cicrles, triangles, stars... You model each with monads, eg. task, maybe, either, functors do compose monads don't compose, so we will focus on normalising the shapes (shows circle interviened) 19:35 - Functional Architecture Patterns on Livestream

Normalize effect types throughout the app

guiding principle.

Monoids

20:56 - Functional Architecture Patterns on Livestream

Will build a validation library.

Defining a semigroup

1 + 2 + 6 => associativity, yu can group the additions as you want closed : doesn't change the type, will still return a number.

2 * 5 * 8 => again associativity, and closed

22:57 - Functional Architecture Patterns on Livestream but 10 / 4 / 2 is not associative or closed.

by the way closed and associative = parallel

more rules

true && true && false again associative

or set intersection

[1, 2, 3] ^ [2, 5, 6] (made up operator) : this is associative as well.

same for disjunction or union.

again closed means we don't change the type, integer stays integer etc.

Semigroups are everywhere.

We can do max and min the same way, or comparisons too are associative and closed.

So we will define this as a data type.

  const Sum = x => ({
  x,
  concat: other => 
    Sum(x + other.x)
})
const res = Sum(3).concat(Sum(5)) // Sum(8)

we're making an interface that has a .concat and satisfies the above laws (associativity and closed).

You can do the same with Product, concat: other=> Product(x * other.x)

Shows the same for Any. Any(x || other.x)

We lift these value into a type, so we can program against an interface.

String has built in .concat.

Monoids

30:22 - Functional Architecture Patterns on Livestream Now will explain how it works as monoids. A monoid is a semigroup with an identity // Monoid = Semigroup + Identity We call it .empty. For Product it would be multiplying by 1. Just an identity function. So you can do Product.empty().concat(Product(10))

Sum.empty = () => Sum(0)

So why the empty matters? It's a starting value. Can be used with reduce.

32:43 - Functional Architecture Patterns on Livestream

Shows the array reduce. const res = [1,2,3,4,5].map(Sum).reduce((acc, n) => acc.concat(n))

So even if we don't have a data process, the empty makes sure that we can still give back a value and not blow up.

35:32 - Functional Architecture Patterns on Livestream

creates the monoid for Any. concat: other => Any(x || other.x)

Any.empty = () => Any(false)

The All monoid: All.empty = () => All(true) (because concat: other => All(x && other.x)

38:05 - Functional Architecture Patterns on Livestream

foldMap introduction

  
const res = [true, true].map(All).reduce((acc, n) => acc.concat(n), All.empty());

// we can simplify this syntax with foldMap:
const { List } = require('immutable-ext') // it has foldMap on the list
const res = List([true, true]).foldMap(All, All.empty())

console.log(res.toJS()) // toJS comes from the immutable library imported by immutable-ext
// actually just run
console.log(res)

A counter example of semigroups that cannot be promoted to monoids.

Intersection

  const Intersection = x => ({
  x,
  concat: other => Intersection(_.intersection(x, other.x))
})

But what would be the empty, starting value? Cannot find one... An empty list would never intersect. Or you would need a list of every possible everything...

So Intersection is a semigroup but not a monoid. But Union can be promoted.

  const res = List([true, true, false]).foldMap(All, All.empty())
// res: true

42:17 - Functional Architecture Patterns on Livestream

Functors

He sometimes uses interchangebly monoids and semigroups because the most important is .concat.

an example with functors:

  const {Id, Task, Either} = require('../lib/types')
const {Left, Right} = Either

// copies the previously created monoids here...

const id = x => x

// you can do things like:
Id.of(Sum(2)).concat(Id.of(Sum(3))) // Id(Sum(5))
console.log(res.fold(id, id)) // { x: 5, concat: [Function: concat]}

So Id is a monoid if what it holds is a monoid. Whatever it's holding.

You always have to program to the interface (not just simple operations, but using the interface of the types)

We can stack these as much as we want:

  const res = Id.of(Right(Task.of(Sum(2)))).concat(Id.of(Sum(3)))

And this will cascade with whatever it's holding.

As long as we are concating with the same shape, it works!

More examples of concating functors together:

  const res = Right('hello').concat(Right('world')) // Right('hello world')
res.fold(console.log, console.log) // hello world


// Left always short circuits :
const res = Right('hello').concat(Left('world')) // Left('world')
res.fold(console.log, console.log) // world

48:41 - Functional Architecture Patterns on Livestream

  // Task will run these in parallel
const res = Task.of('hello').concat(Task.of('world')) 
res.fold(console.log, console.log) // hello world


const res = Task.of(['hello']).concat(Task.of(['world'])) 
res.fold(console.log, console.log) // ['hello', 'world']

// Rejected is like Left, only gives the failed branch
const res = Task.of(['hello']).concat(Task.rejected(['world'])) 
res.fold(console.log, console.log) // 'world'

Same behaviour as Left. Or like Promise.all. Stops if we have a single failure. But later will show an example of being even more flexible in handling failures.

Promise.all is like a traversable we can flip the "Task" holding a list to a List of Tasks (or vice versa).

Here, we are folding down, and combining them all, but more powerful as we can combine them in several ways.

The closest thing to a silver bullet as you can get (talking about monoids)

51:19 - Functional Architecture Patterns on Livestream

  tryCatch(() => readFileSync()) // Right || Left

// so instead of concat giving the left like this:
Right('hi').concat(Left('bye')) // Left('bye')

// there's a construct called Alternative that captures choice
// but we can also do it with monoids by wrapping the above:

const Alternative = ex => ({  // ex meaning holding an Either of x
  ex,
  concat: other => 
    Alternative(other.ex.isLeft ? ex : ex.concat(other.ex))
})
// reminder: never fall out of the type, when concating so wrap
// the result in Alternative
// 
// in the above implementation, we can make choices and instead of
// `ex.concat(other.ex)` we could just keep `other.ex`, we can decide
// but here, to keep the intuition of monoids we concat the two

const res = Alternative(Right('hi').concat(Alternative(Left('bye'))))
console.log(res.ex.fold(id, id)) // hi

// another test
const res = Alternative(Right('hi'))
  .concat(Alternative(Right('!!!!!'))) 
  .concat(Alternative(Left('bye'))) 

console.log(res.ex.fold(id, id)) // hi!!!!

with Alternative you can decide how to branch your code and and only concat the inside if it's a Right.

Will rewrite the next as a foldMap to simplify a bit, using List:

  const {List} = require('immutable-ext')

const res = List([Right('hi'), Left('ab'), Right('!')])
  .foldMap(Alternative, Alternative(Right(''))) //second argument is the initial value, here a Right of empty string
console.log(res.ex.fold(id, id)) // hi!

58:38 - Functional Architecture Patterns on Livestream You can use Class syntax if you want to make monoids. You will be making tem more often than functors.

CodePen:

CodePen example and exercises 1:00:51 - Functional Architecture Patterns on Livestream exercises time starting

back from the break: 1:16:19 - Functional Architecture Patterns on Livestream Going through the exercises. Using his own library to add foldmap to immutable Map.

First exercise

  // Ex1: reimplement sum using foldMap and the Sum Monoid
// =========================

var sum = xs => List(xs).reduce((acc, x) => acc + x, 0)

// solution:
var sum = xs => List(xs).foldMap(Sum, Sum.empty())


QUnit.test("Ex1: sum", assert => {
  assert.equal(String(sum([1,2,3])), "Sum(6)")
  })

Q: How does this work exactly ?

Let's rewrite foldMap:

  const foldMap = (t, empty, xs) => xs.reduce((acc, x) => acc.concat(t(x)) ,empty )

// or, we could *first* `map` it to the type, and then reduce it
const foldMap = (t, empty, xs) => xs.map(t).reduce((acc, x) => acc.concat(x) ,empty )

1:20:13 - Functional Architecture Patterns on Livestream Now, there's also the fold function: We could have it like this:

  var sum = xs => List(xs).map(Sum).fold(Sum.empty())

Now, fold and map are not specialised to lists! You can do it on trees, event streams, many different data structures. Even with Either you can .fold.map out of the either.

foldMap is very useful, it comes from the foldable interface. It's one of the most useful methods in functional programming.

Pointed functors and the solution above

Immutable.js has pointed functors, so you can do List.of(2). but List.of([1,2,3]) // List([1,2,3]) would actually put the array into the List.

Second exercise

These are just "toy" exercises, to get the feeling for foldMap. The types cascade, so you can combine effects, branches and intense calculations with foldMap.

  // Ex2: reimplement lessThanZero using foldMap and the Any Monoid
// =========================

// original
var anyLessThanZero = xs =>
  List(xs).reduce((acc, x) => acc < 0 ? true : false, false)

// solution
var anyLessThanZero = xs =>
  List(xs).reduce((acc, x) => acc.concat(x < 0 ? Any(true) : Any(false)), Any.empty())
// NOTE, that above we take a number (0) and convert it to a boolean, because Any takes a boolean

// or another solution from the audience
var anyLessThanZero = xs =>
  List(xs).foldMap(x => Any(x < 0), Any.empty())

// or another solution from the audience
var anyLessThanZero = xs =>
  List(xs).map(x => x < 0).foldMap(Any, Any.empty())
// or
var anyLessThanZero = xs =>
  List(xs).map(x => Any(x < 0)).fold(Any.empty())

QUnit.test("Ex2: anyLessThanZero", assert => {
  assert.equal(String(anyLessThanZero([-2, 0, 4])), "Any(true)")
     assert.equal(String(anyLessThanZero([2, 0, 4])), "Any(false)")
     })

Next exercise

  // Ex3: Rewrite the reduce with a Max monoid (see Sum/Product/Any templates above)
// =========================

var max = xs =>
  List(xs).reduce((acc, x) => acc > x ? acc : x, -Infinity)

// solution
// making the Max monoid
const Max = x =>
({
  x: x,
  concat: other => Max(x > other.x ? x : other.x),
  toString: () => `Max(${x})`
})
Max.empty = () => Max(-Infinity)

var max = xs =>
  List(xs).foldMap(Max, Max.empty()) // test passing!!

QUnit.test("Ex3: max", assert => {
  assert.equal(String(max([-2, 0, 4])), "Max(4)")
    assert.equal(String(max([12, 0, 4])), "Max(12)")
    })

Next exercise

1:28:31 - Functional Architecture Patterns on Livestream

  // Ex4 (Bonus): Write concat for Tuple
// =========================

const Tuple = (_1, _2) => ({
  _1,
  _2,
  concat: o => undefined, // write me
});

//  write it

const Tuple = (_1, _2) => ({
  _1,
  _2,
  concat: o =>
    Tuple(_1.concat(o._1), _2.concat(o._2))
});

QUnit.test('Ex4: tuple', assert => {
  const t1 = Tuple(Sum(1), Product(2));
  const t2 = Tuple(Sum(5), Product(2));
  const t3 = t1.concat(t2);
  assert.equal(String(t3._1), 'Sum(6)');
  assert.equal(String(t3._2), 'Product(4)');
});

1:30:16 - Functional Architecture Patterns on Livestream

More examples

  const getAppAplerts = () => fetch('/alerts').then(x => x.json())
const getDirectMessages = () => fetch('/dm').then(x => x.json())

getAppAplerts().concat(getDirectMessages())
// Promise([{id:1, msg: 'Policy update'}, {id:2, msg: 'hi from spain'}])

In a pure functional setting you would be using Task and not Promise... It's associative and closed, so for example can be done parallel.

Another example

  const getPost = () => fetch('/post')
.then(x => x.json())
.then(Map)

const getComments = () => fetch('/comments')
.then(x => x.json())
.then(comments => Map({comments}))

getPost().concat(getComments())
// Promise(Map({id:3, body: 'Redux is over', comments: []}))

Map is a way to define a semigroup on an object. If there's a merge conflict on a key it will just concat the merge conflict.

1:32:03 - Functional Architecture Patterns on Livestream

another example

try/catch is not very pure.

  const tryCatch = fn => args => {
  try {
    return Right(fn(args))
  } catch(e) {
    return Left(e)
  }
}

const readFile = tryCatch(fs.readFileSync)
const filepaths = ['one.txt', 'two.txt', 'three.txt']

filepaths.foldMap(readFile, Right(''))
// Right('Everything Brian tells you is a lie dont listen to him')

If any of the file reads would blow up, we would just get the Left.

Same thing but async

  const readFile = promisify(fs.readFile)
const filepaths = ['one.txt', 'two.txt', 'three.txt']

filepaths.foldMap(readFile, Promise.resolve(''))
// Promise('Everything Brian tells you is a lie dont listen to him')

now it's asynchronous.


  const getWords = str => str.match(/\w+/g)
filepaths.foldMap(path => readFile(path).then(getWords), Promise.resolve([]))
// Promise(['I', 'survived', 'off', 'of', 'the', 'painted', 'easter', 'eggs'])

an example of doing null checks

  const nullCheck = x =>
  x != null ? Right(x) : Left('got null')

const getWords = str => nullCheck(str.match(/\w+/g))
filepaths.foldMap(path => readFile(path).then(getWords), Promise.resolve(Right([])))
// Promise(Right(['I', 'survived', 'off', 'of', 'the', 'painted', 'easter', 'eggs']))


// but, if there's a null:
// Promise(Left('got null'))

Another example

  const filepaths = ['db.json', 'host.json']
const readCfg = path => tryCatch(() => Map(require(path)))

filepaths.foldMap(readCfg, Right(Map()))
// Right(Map({hostnameY 'localhost', port: 8888, dbName: 'testDb'}))

Tree concat example

  const Report = el =>
  Map({
    elementcount: Sum(1),
    classes: Set(el.classList),
    tags: Set(el.tagName),
    maxHeight: Max(el.height)
  })

Report.empty = () =>
  Map({
    elementcount: Sum.empty(),
    classes: Set(),
    tags: Set(),
    maxHeight: Max.empty()
  })

const getReport = root => 
  Tree(root, x => Array.from(x.children)).foldMap(Report, Report.empty())

// Tree is a functor/foldable monoid

getReport(document.body)
//Map({
//  elementcount: Sum(1292),
//  classes: I.Set(({'slds-button', ...})),
//  tags: I.Set({'a', 'button', ...}),
//  maxHeight: Max(592)
//});

Map will combine and do automatic conflict resolution Each element combine differently, but foldMap can still concat them because of they all obey the same rules.

And to fold a list of documents:

  docs.foldMap(doc => getReport(doc.body), Report.empty())

//Map({
//  elementcount: Sum(21292),
//  classes: I.Set(({'slds-button', ...})),
//  tags: I.Set({'a', 'button', ...}),
//  maxHeight: Max(831)
//});

1:35:53 - Functional Architecture Patterns on Livestream

Some rules

  x.concat(y.concat(z)) == x.concat(y).concat(z)
x.concat(M.empty()) == M.empty().concat(x) == x

associativity, identity rules

Homomorphisms

  f(M.empty()) == M.empty()
f(x.concat(y)) == f(x).concat(f(y))

We can take a monoid, and do a type transformation to another monoid, and the properties will still hold.

  [].length == 0
xs.concat(ys).length == xs.length + ys.length

MyMessage message;
message.ParseFromString(str1 + str2);

MyMessage message, message2;
message.ParseFromString(str1);
message2.ParseFromString(str2);
message.MergeFrom(message2);

so we can parse one message and another one and merge them or, we can combine the messages and parse them.

1:36:44 - Functional Architecture Patterns on Livestream

Cayley's Theorem

  const xs = ['a', 'b', 'c'].map(x => Endo(y => x.concat(y)))
// [Endo(y => 'a'.concat(y)), Endo(y => 'b'.concat(y))]

xs.map(x => x.run(''))
// ['a', 'b', 'c']

Monads

Monads are monoids in the category of endofunctors

Because .chain is a monoidal operation.

1:37:10 - Functional Architecture Patterns on Livestream

Making a validation library

Validations are things that combine.

  isPresent(obj.name).concat(isEmail(obj.email))

We should get the combined errors or the success.

First, let's the API we want to end up with:

  const validations = {
  name: isPresent,
  email: isEmail.concat(isPresent)
}
validate(validations, obj)

Writing the validate function.

  const validations = { name: isPresent, email: isPresent }
const obj = {name: 'brian', email: 'brian@brian.com'}
const res = validate(validations, obj)

console.log('V',res)

So first, the validate function:

  const isPresent = x => !!x

const validate = (spec, obj) => 
  List(Object.keys(spec)).foldMap(
  key => {
    spec[key](obj[key]) ? Right(First(obj)) : Left(`${key} bad`)
  },
  Either.of(First(obj))
  )

It should work, because we're returning an Either and it does concat. In a second update, we added the First wrapping above, but it would not actually collect all the errors, it would short circuit on the first error.

Or, if we wrap all in an array, that does concat

  const validate = (spec, obj) => 
  List(Object.keys(spec)).foldMap(
  key => {
    spec[key](obj[key]) ? Right([obj]) : Left([`${key} bad`])
  },
  Either.of([obj])
  )

res.fold(console.log, console.log)

With this implementation, we still only get one error, and all the others are ignored.

Let's make the a success and failure types to collect all the validation results. These two will be a "subclass" of some other validation type, just like Right and Left and Either. So it's closed it will always return either a success or failure, just like the Either sub types. You always get back an Either, but it can be a Right or a Left.

  const Success = x => ({
  isFail: false,
  x,
  fold: (f,g) => g(x), // fold out, run the success case
  concat: other => 
    other.isFail ? other : Success(x) // so we collect all the failures
})

const Fail= x => ({
  isFail: true,
  fold: (f,g) => f(x), // get out of the type, run the failure case
  x,
  concat: other => 
    other.isFail ? Fail(x.concat(other.x)) : Fail(x) // This is like the opposite of Either, the failures are concating
})

// and to use them:
const validate = (spec, obj) => 
  List(Object.keys(spec)).foldMap(
  key => {
    spec[key](obj[key]) ? Success([obj]) : Fail([`${key} bad`])
  },
  Success([obj])
  )

res.fold(console.error, console.log)

1:55:58 - Functional Architecture Patterns on Livestream

This is the basics of a pretty solid validation library.

improving the validation library

Instead of concating the results, we can concat the entire validation itself.

we could wrap it with Validation so we have a Validation holding the function and now we can combine these

  const isPresent = Validation(x => !!x)

Or, demand the isPresent returns a Success or Fail

  const isPresent = Validation(x => !!x ? Success(x): Fail(['needs to be present']))

But we're missing the key for the validation message. So refactor one more time, accepting the key

  const isPresent = Validation((key, x) => 
  !!x 
  ? Success(x) 
  : Fail([`${key} needs to be present`]))

Notice, the array in Fail. Because we want to concat all the Fails and Array does it automatically (free monad).

Creating the generic Validation monoid

First, we update the validate function, to use Validation which will have a run method.

  const validate = (spec, obj) => 
  List(Object.keys(spec)).foldMap(
  key => {
  // 1. we add .run here
  //  spec[key].run(obj[key]) ? Success([obj]) : Fail([`${key} bad`])
  // 2. and can remove all the rest finally
  spec[key].run(key, obj[key])
  },
  Success([obj])
  )


// now we can concat the different Validations
const validations = {name: isPresent, email: isPresent.concat(isEmail)}

Writing the isEmail validaton:

  const isEmail = Validation((key, x) => 
  !!/@/.test(x)
  ? Success(x) 
  : Fail([`${key} must be an email`]))

and finally the Validation monoid:

  const Validation = run => ({
  run,
  concat: other => 
    Validation((key, x) => run(key, x).concat(other.run(key, x))) // we're concating running our validation with running the other validation with the same key and x
})

Now everything is working well! It lists all the possible validation errors or just the object.

2:01:59 - Functional Architecture Patterns on Livestream

More validation possibilities

Doing an Or combination:

  const validations = {name: isPresent, email: Or(isPresent).concat(Or(isEmail))}

And and Or form a semiring, they interact and distribute between each other. The Or would mean that you should give an email, but it's OK if it fails.

Validation is finished now. Exports it to be used later in the course.

Function modeling

2:03:25 - Functional Architecture Patterns on Livestream

Actually we already did function modeling with validation (in the Validation funtion. Instead of modeling a data and a type of data, we were modeling a function.

We provided a run argument and we would run it.

  const Validation = run => ({
  run,
  concat: other => 
    Validation((key, x) => run(key, x).concat(other.run(key, x))) 
})

Let's see more examples of function modeling:

  const toUpper = x => x.toUpperCase()
const exclaim = x => x.concat('!')

// we want to combine them...
// run the two functions and concat their results
// but also return the same type

const Fn = run => ({
  run,
  // 2. let's add `map` to make if a functor
  map: f => Fn(x => f(run(x))),
  concat: other =>
    Fn(x => run(x).concat(other.run(x)))
})

So functions are like functors, two "boxes" and we can combine them. concat can "open up" the box and concat what's inside.

  const res = Fn(toUpper).concat(Fn(exclaim)).run('fp sux')
console.log(res) // FP SUXfp sux!

So above, both functions run, they returned a String that has .concat and concated them.

And we can even map over the result

  const res = Fn(toUpper).concat(Fn(exclaim)).map(x => x.slice(3)).run('fp sux')
console.log(res) // SUXfp sux!

functions as monads

Monads are about nesting

  Fn(x => Fn(y => x, y))
// we will flatten these two with monads
  let's write a function monad, continuing from above
const Fn = run => ({
  run,
  // add the chain method
  // we need to return a function on the "outside"
  chain: f => Fn(x => f(run(x)).run(x)),
  map: f => Fn(x => f(run(x))),
  concat: other =>
    Fn(x => run(x).concat(other.run(x)))
})

// example:
// now, we have another argumetn `y` below!
const res = Fn(toUpper).chain(upper => Fn(y => exclaim(upper))).run('hi')
console.log(res) // HI!

So what's interesting is that in the .chain above, f(run(x)) run with the 'hi' value, but then the .run(x) on it again run with the 'hi', this is why it uppercased and exclaimed it. (we didn't use the y variable).

If we would do, we would get

  const res = Fn(toUpper).chain(upper => Fn(x => exclaim(x))).run('hi')
console.log(res) // hi!

or, we could get both vars:

  const res = Fn(toUpper).chain(upper => Fn(x => [upper, exclaim(x)])).run('hi')
console.log(res) // ['HI', 'hi!']

This is why ha said it was reader because x was carried through and we could get access to it any time. As we start transforming our input, we can still get back to the original value.

The Reader Monad

  Fn.of = x => Fn(() => x)
const res = Fn.of('hello')
  .map(toUpper)
  .chain(upper => Fn(x => [upper, exclaim(x)]))
  .run('hi')
console.log(res) // ['HELLO', 'hi!']

We got two arguments!

2:14:04 - Functional Architecture Patterns on Livestream

So we can now have a pattern, where the second argument is provided later when running the app:

  Fn.of = x => Fn(() => x)
const res = Fn.of('hello')
  .map(toUpper)
  .chain(upper => Fn(config => [upper, exclaim(config)]))
console.log(res.run({port:3000})) // ['HELLO', {port; 3000}]

add an .ask method

  Fn.ask = Fn(x => x)
const res = Fn.of('hello')
  .map(toUpper)
  .chain(upper => 
    Fn.ask.map(config => [upper, config]))

console.log(res.run({port:3000})) 

So this is the reader monad, and is extremely useful in functional architectures.

Some libraries like zeo or neo have started working on this more in detail. You can do dependency incjection.

  console.log(res.run({db, strategy})) // just pass in your config

More functions we can model

State is exactly like this (won't present it). we can thread a state through our program and modify it, it's the exact same pattern. The difference is that you can modify the original state you pass into it. He doesn't find it as useful.


What if we make a function composition as a way of concating values? For this we need a type Endo

  // this is where we want to arrive:
[toUpper, exclaim].foldMap(Endo, Endo.empty().run('hello')) // HELLO!

It's called endo beause it only works with endomorphism ie where the type doesn't change between input and output:

  a -> a
String -> String
Task -> Task

This guarantees that we can compose the functions because the types won't change.

Create the Endo function

Based on the Fn above. since we have endomorphism, we cannot have a map or chain method. so it cannot be a functor.

  const Endo = run => ({
  run,
  concat: other =>
    Endo(x => run(other.run(x))) // "we run the other one and then run mine with the result"
})
// 2. Endo.empty definition (empty doesn't take arguments)
Endo.empty = () => Endo(x => x)

const res  = List([toUpper, exclaim])
  .foldMap(Endo, Endo.empty())
  .run('hello')

console.log(res)

Contravariant functors

Always return the same type

  a -> String

Can do it with sort functions, predicate functions...

Predicate functions: a -> Bool (always return a boolean)

So we can always map over the argument.

  // (acc, a) -> acc
// so `acc` is fixed, cannot map over its type
// but we can change a -> b
const Reducer = run => ({
  run,
  contramap: f => 
    // instead of writing map that maps over the output, we map over the **input**
    Reducer((acc, x) => run(acc, f(x))),
    // so with `f(x)` we transform our value _before_ it got to the run function  
  concat: other =>
    Reducer((acc, x) => other.run(run(acc, x), x))
})

Reducer(login).concat(Reducer(changePage)).run({user: {}, env: {...}}) // so it's a kind of payload (in the `run`)

// so we can contramap:
Reducer(login.contramap(payload => payload.user))
  .concat(Reducer(changePage).contramap(payload => payload.currentPage))
  .run(state, {user: {}, currentPage: {...}}) 

We just combined two reducers, both of which have been contramapped. So the each take one input but transform them to what they are looking for.

So one use case is to keep the entire payload, but plucking parts off for the different inner functions (like above).

We're combining different functions, they take different inputs, but I only run run once. It's almost like a before hook, while .map is more like an after hook.

When you want to combine functions, you may want to manipulate stuff before they get there...

2:27:45 - Functional Architecture Patterns on Livestream so it's really when you are combining functions into one, and they just receive one function. Also when you want to pre-compose instead of post-compose when you are building your application and you have a fixed output and variable input.

Function modeling exercise

CodePen exercise

3:33:55 - Functional Architecture Patterns on Livestream

The proper Endo function!!

  // Definitions
const Endo = run => ({
  run,
  concat: other => Endo(x => other.run(run(x)))
});

Endo.empty = () => Endo(x => x);

We have a bunch functions that work with strings and we can turn them into Endos.

  // Ex1:
// =========================

const classToClassName = html => html.replace(/class\=/gi, 'className=');

const updateStyleTag = html => html.replace(/style="(.*)"/gi, 'style={{$1}}');

const htmlFor = html => html.replace(/for=/gi, 'htmlFor=');

const ex1 = html => htmlFor(updateStyleTag(classToClassName(html))); //rewrite using Endo

// ***** Solution ******

const ex1 = html => 
  Endo(htmlFor)
  .concat(Endo(updateStyleTag))
  .concat(Endo(classToClassName))
  .run(html)

// So we are just capturing function compositions via monoids


// ***** Solution 2 ******

// put them in a List

const ex1 = html => 
  List.of(htmlFor, updateStyleTag, classToClassName)
  .foldMap(Endo, Endo.empty())
  .run(html)

// or just a plain array and reduce

const ex1 = html => 
  [htmlFor, updateStyleTag, classToClassName]
  .reduce((acc, x) => acc.concat(Endo(x)), Endo.empty())
  .run(html)

// but foldMap is a common pattern that you will see
// often in other languages


QUnit.test('Ex1', assert => {
  const template = `
		<div class="awesome" style="border: 1px solid red">
			<label for="name">Enter your name: </label>
			<input type="text" id="name" />
		</div>
	`;
  const expected = `
		<div className="awesome" style={{border: 1px solid red}}>
			<label htmlFor="name">Enter your name: </label>
			<input type="text" id="name" />
		</div>
	`;

  assert.deepEqual(expected, ex1(template));
});

Also note that the other of functions passed into the list does not matter.

Predictes

We will do contramap.

3:37:41 - Functional Architecture Patterns on Livestream

  // Ex2: model a predicate function :: a -> Bool and give it contramap() and concat(). i.e. make the test work
// =========================
const Pred = undefined; // todo

const Pred = run => ({
  run,
  // we cannot have a .map, since it always has
  // to return a bool
  contramap: f => 
    Prod(x => run(f(x))),
  concat: other =>
    Pred(x => run(x) && other.run(x)) // both return true|false so we can do && which is equivalent of concat for Bool
})




QUnit.test('Ex2: pred', assert => {
  const p = Pred(x => x > 4)
    .contramap(x => x.length)
    .concat(Pred(x => x.startsWith('s')));
  const result = ['scary', 'sally', 'sipped', 'the', 'soup'].filter(p.run);
  assert.deepEqual(result, ['scary', 'sally', 'sipped']);
});

Again a high level explanation of contramap

Take the following predicates:

  // takes a number:
const greaterThanFour = Pred(x => x >4)
// takes a string:
const sStart = Pred(x => x.startsWith('s'))

So one returns a string, the other a number, and to check the length, I don't want to transform the original value, but I can do this transformation from string to number just before I call the predicate. I add a hook to be able to change the value before I pass to a function.

Contravarian functor when you have a .contramap functor.

If you have a .contramap and also .map it's called a profunctor. There's a more detailed graph on the fantasy-land GitHub repo /figures/dependencies.png

Last exercise

Builds on the previous one, add matchesAny predicate.

  // Ex3:
// =========================
const extension = file => file.name.split('.')[1];

const matchesAny = regex => str => str.match(new RegExp(regex, 'ig'));

const matchesAnyP = pattern => Pred(matchesAny(pattern)); // Pred(str => Bool)

// TODO: rewrite using matchesAnyP. Take advantage of contramap and concat
const ex3 = file =>
  matchesAny('txt|md')(extension(file)) &&
  matchesAny('functional')(file.contents);

// ***** Solution *****
const ext3 = file =>
  matchesAnyP('txt|md').contramap(extension)
    .concat(matchesAnyP('functional').contramap(f => f.contents))
    .run(file)


QUnit.test('Ex3', assert => {
  const files = [
    { name: 'blah.dll', contents: '2|38lx8d7ap1,3rjasd8uwenDzvlxcvkc' },
    {
      name: 'intro.txt',
      contents: 'Welcome to the functional programming class'
    },
    { name: 'lesson.md', contents: 'We will learn about monoids!' },
    {
      name: 'outro.txt',
      contents:
        'Functional programming is a passing fad which you can safely ignore'
    }
  ];

  assert.deepEqual([files[1], files[3]], files.filter(ex3));
});

The implications of this architecture

On most applications you plug together different systems and you need to constantly transform the types to match the expected inputs. With monoids you don't need adapters, you only have one piece and combine these.

3:47:09 - Functional Architecture Patterns on Livestream So above we are normalising our types into Predicates and then it's easy to combine them.

Getting back to the previous error with Endo

From before, this was not the proper implementation of Endo:

  const Endo = run => ({
  run,
  concat: other =>
    Endo(x => run(other.run(x))) 
})
Endo.empty = () => Endo(x => x)

const res  = List([toUpper, exclaim])
  // .foldMap(Endo, Endo.empty())
  // he was tryng to much to pass the empty string:
  .foldMap(Endo, Endo.empty(''))
  .run('hello')

console.log(res)

He was trying to add an empty string to Endo.empty('') BUT, Endo gets its arguments later, when you run .run And the definition of Endo.empty is the first value you pass to it (on .run)

3:48:25 - Functional Architecture Patterns on Livestream

Reducer and redux architecture

  const Reducer = run => ({
  run,
  contramap: f => 
    Reducer((acc, x) => run(acc, f(x))),
  concat: other =>
    Reducer((acc, x) => other.run(run(acc, x), x)) // this returns a new accumulator, which gets passed to the next one(`other`)
})

const checkCreds = (email, pass) =>
   email === 'admin' && pass === 123

const login = (state, payload) =>
  payload.email 
  ? Object.assign({}, state, {loggedIn: checkCreds(payload.email, payload.pass)})
  : state // "we always have to return an accumulator"

const setPrefs = (state, payload) =>
  payload.prefs
  ? Object.assign({}, state, {prefs: payload.prefs})
  : state

// we put both of them in a Reducer, so we can combine them
const reducer = Reducer(login).concat(Reducer(setPrefs))

const state = {loggedIn: false, prefs: {}}
const payload = {email: 'admin', pass: 123, prefs: {bgcolor: '#000'}}
console.log(reducer.run(state, payload))

Now, we can play around with the types and this is why it's great that we can model functions.

  // (acc, a) -> acc
// now, play around with the laws, each line is
// equivalent to the previous one:
// (a, acc) -> acc // (we can flip around the functions)
// a -> acc -> acc (isomorphism), we can take one argument at a time
// a -> (acc -> acc) // this is Endo !!
// a -> Endo(acc -> acc)

// Fn(a -> Endo(acc -> acc))
const Reducer = run => ({
  run,
  contramap: f => 
    Reducer((acc, x) => run(acc, f(x))),
  concat: other =>
    Reducer((acc, x) => other.run(run(acc, x), x)) // this returns a new accumulator, which gets passed to the next one(`other`)
})

3:53:15 - Functional Architecture Patterns on Livestream so now we can do:

  // 1.
// const reducer = Reducer(login).concat(Reducer(setPrefs))
const reducer = Fn(login).concat(Fn(setPrefs))

// 2. then flip and curry the arguments for login
// const login = (state, payload) =>
const login = payload => Endo(state => 
  payload.email 
  ? Object.assign({}, state, {loggedIn: checkCreds(payload.email, payload.pass)})
  : state 
)

continue the modeling:

  const reducer = Fn(login).map(Endo).concat(Fn(setPrefs))

// and now you run it like this:
console.log(reducer.run(payload).run(state))

So now we separated the two, payload and state.

Or, you can do it like this, unwrapping the Endo in login and mapping it in reducer:

  const login = payload => state => 
  payload.email 
  ? Object.assign({}, state, {loggedIn: checkCreds(payload.email, payload.pass)})
  : state 

// add the .map in the end:
const reducer = Fn(login).map(Endo).concat(Fn(setPrefs).map(Endo))

3:55:31 - Functional Architecture Patterns on Livestream We can even factor state out.

Transformers

Monad transformers and functor composition

  const Compose = (F, G) => {
  const M = fg => ({
    extract: () => fg,
    map: f => M(fg.map(g => g.map(f)))
  })
  M.of = x => M(F.of(G.of(x)))
  return M
}

So Compose returns M and if we want to put it in M (M.of), we put in G, then F.

So how does this work?

  const TaskEither = Compose(Task, Either) // Task in the outside, either in the inside

TaskEither.of(2)
.map(two => two * 10)
.map(twenty => twenty + 1)
// it's a Compose Task of Either, so need to extract it
// to get out the value
.extract()
// so now it's a Task holding an either
.fork(console.error, either => 
  either.fold(console.log, console.log)
)

so notice that above, I only had to do .map not several.

3:59:06 - Functional Architecture Patterns on Livestream

on the extract

Extract is similar to fold but doesn not take the function, it just gets out the value

So functors compose:

  // Id is also a functor...
const TaskEither = Compose(Id, Compose(Task, Either))

4:00:52 - Functional Architecture Patterns on Livestream

If I chain however, I would need to create another TaskEither to do it. So you cannot always, mechanically compose to monads.

  TaskEither.of(2)
.chain(...) // ???
.map(twenty => twenty + 1)
.extract()
.fork(console.error, either => 
  either.fold(console.log, console.log)
)

We cannot add .chain to our Compose.

We can combine Task and Either into a monoid.

But typically you want to be able to .map and .chain to be able to compose their insides.

So the above code was a demonstration that funtors compose but monads do not.

We will define a monad transformer.

A demo where a transformer is useful

  const _ = require('lodash')

const users = [{id: 1, name: 'Brian'}, {id: 2, name: 'Marc'}, {id: 3, name: 'Odette'}]
const following = [{user_id: 1, follow_id: 3}, {user_id: 1, follow_id: 2}, {user_id: 2, follow_id: 2}]

// so here we have the Either inside a Task
const find = (table, query) =>
  Task.of(Either.fromNullable(_.find(table,query)))

// we want to find the followers of user_id:1
const app = () =>
  find(users, {id: 1}) // Task(Either(User))
  .chain(eu => // eu : EitherUser
  // since we call .chain, we have to return another Task

    eu.fold(Task.rejected, u => find(following, {follow_id: u.id}))
  ).chain(eu => 
    eu.fold(Task.rejected, fo => find(users, {id: fo.user_id}))
    
  )
  .fork(console.error, eu => 
    eu.fold(console.error, console.log)
  )

  app()

So this is a demonstration that the code becomes very complex with all the .chains and folds to look up values in the linked table.

Solution with monad transformers

  // the require was there from the beginning:
const { TaskT, Task, Either } = require('../lib/types')
// make a Task transformer
const TaskEither = TaskT(Either)
// TaskT knows how to .chain.chain

// each Monad should have it's own transformer, 
// eg. EitherT, IdT
// because each monad can only know how to deal
// with its own effects

// to recover Task, just pass the Id functor
const Task = Task(Id)
// (it's just an interesting property we can exploit)

// so now to refactor:
//const find = (table, query) =>
//  Task.of(Either.fromNullable(_.find(table,query)))

const find = (table, query) =>
  TaskEither.lift(Either.fromNullable(_.find(table,query)))
// note, that above, we're using .lift so
// that we don't end up Task(Either(Either(x))) wrapped Eithers
// so .lift is like .of but doesn't duplicate the inner type

const app = () =>
  find(users, {id: 1})
  // and now refactoring
  .chain(u => find(following, {follow_id: u.id})) // Task(Either(User))
  .chain(f => find(users, {id; fo.user_id})) // Task(Either(User))
  // so still fork the either etc etc
  .fork(console.error, eu => 
    eu.fold(console.error, console.log)
  )

4:11:14 - Functional Architecture Patterns on Livestream

This is a typical "stack" when you are working in functional programming in many apps:

  const { FnT, TaskT, Task, Either, EitherT  } = require('../lib/types')


const FnTask = FnT(Task)
const App = EitherT(FnTask)

// App :: Either(Fn(Task))

const res = App.of(2).map(x => x + 1) // App(3)
// but at this point App is holding an Either, holding a Function holding a Task

res.fold(console.error, fn => 
  fn.run({myEnv: true})
  .fork(console.error, console.log)
  )

continues..., this works as well:

  // const res = App.of(2).map(x => x + 1) // App(3)
const res = App.of(2).chain(x => App.of(x + 1)) 

res.fold(console.error, fn => 
  fn.run({myEnv: true})
  .fork(console.error, console.log)
  )

continues..., how to deal when we have an Either inside?

  // const res = App.of(2).chain(x => App.of(x + 1)) 
const res = App.of(2).chain(x => Either.of(x + 1)) 
// Either is the outer type, so we need to put 
// the types inside it:
const res = App.of(2).chain(x => Either.of(FnTask.of(x + 1))) 
// but it's still not an App "type"
// need to "lift" it:
const res = App.of(2).chain(x => App.lift(Either.of(FnTask.of(x + 1)))) 

res.fold(console.error, fn => 
  fn.run({myEnv: true})
  .fork(console.error, console.log)
  )

4:16:56 - Functional Architecture Patterns on Livestream At the above point Brian got lost in the code so let's simplify it:

  const FnTask = FnT(Task)
// const App = EitherT(FnTask)
const App = FnT(FnTask)
// now we have two "environments" playing at once


const TaskEither = TaskT(Either)

TaskEither
.of(2)
.chain(two => TaskEither.lift(Either.of(two + two)))
.fork(console.error, x => x.fold(console.log, console.log))

Let's break it down one more level:

  const EitherId = EitherT(Id)
const TaskEither = TaskT(EitherId)

TaskEither
.of(2)
// .chain(two => TaskEither.lift(Either.of(two + two)))
.chain(two => TaskEither.lift(EitherId.of(two + two)))
.fork(console.error, x => 
  x.fold(console.log, y => console.log(y.extract()))
)

Actually the above code does not run, and Brian didn't manage to get it working quickly, but the point anyway is that this is not a good pattern.

4:20:57 - Functional Architecture Patterns on Livestream

Free monads

When we want to model a function that has state, environment and async, and error handling -> you get into situations like above, you have to bee good in lifting types out.

CodePen exercises

4:39:41 - Functional Architecture Patterns on Livestream Coming back to the previous exercises. The bug was coming from his types "definitions" file.

  const TaskEither = TaskT(Either)
const App = FnT(TaskEither)

App
.of(2)
.chain(two => App.lift(TaskEither.of(two + two)))
.chain(four => App.lift(TaskEither.lift(Either.of(four))))
.chain(four => App.lift(Task.of(four).map(Either.of)))
.run({})
.fork(console.log, fi => fi.fold(console.log, console.log))

now play with the above:

  App
.of(2)
.map(two => two * 2) // .map is simple
// but chain is more complex, we have to think about
// what our function is going to return
// for example .chain(doDbThing) -> we have to think about
// how to get out the types
// you have to manually put the shapes together

// for example in the next line we have .lift into our App
.chain(two => App.lift(TaskEither.of(two + two)))

// here we have Either.of(four) so need to lift that to
// TaskEither
.chain(four => App.lift(TaskEither.lift(Either.of(four))))

// here, we're returning a Task and neet do .map that...
.chain(four => App.lift(Task.of(four).map(Either.of)))
.run({})
.fork(console.log, fi => fi.fold(console.log, console.log))

Exercises

  // Ex1: 
// =========================
const FnEither = FnT(Either)
const {Right, Left} = Either

// TODO: Use FnEither.ask to get the cfg and return the port
const ex1 = () =>{}

const ex1 = () => 
  FnEither.ask.map(config => config.port)
// "we just pulled the config out thin air"
// (meaning it was provided in the .run() below)

QUnit.test("Ex1", assert => {
	const result = ex1(1).run({port: 8080}).fold(x => x, x => x)
	assert.deepEqual(8080, result)
})
  
// Ex1a: 
// =========================
const fakeDb = xs => ({find: (id) => Either.fromNullable(xs[id])})

const connectDb = port =>
    port === 8080 ? Right(fakeDb(['red', 'green', 'blue'])) : Left('failed to connect')

// TODO: Use ex1 to get the port, connect to the db, and find the id
const ex1a = id =>{}

const ex1a = id => 
  // ex1 is FnEither holding a port
  ex1().chain(port => 
    FnEither.lift(connectDb(port)) // lift, because connectDb is also an Either 
  )
  .chain(db => FnEither.lift(db.find(id)))

QUnit.test("Ex1a", assert => {
	assert.deepEqual('green', ex1a(1).run({port: 8080}).fold(x => x, x => x))
	assert.deepEqual('failed to connect', ex1a(1).run({port: 8081}).fold(x => x, x => x))
})

Brian repeats that monad transformers are pretty painful (especially in JavaScript).

  
// Ex2: 
// =========================
const posts = [{id: 1, title: 'Get some Fp'}, {id: 2, title: 'Learn to architect it'}, {id: 3}]

const postUrl = (server, id) => [server, id].join('/')

const fetch = url => url.match(/serverA/ig) ? Task.of({data: JSON.stringify(posts)}) : Task.rejected(`Unknown server ${url}`)

const ReaderTask = FnT(Task)

// Use ReaderTask.ask to get the server for the postUrl
const ex2 = id =>
	fetch(postUrl(server, id)).map(x => x.data).map(JSON.parse) // <--- get the server variable from ReaderTask

const ex2 = id =>
  ReaderTask.ask.chain(server => 
    ReaderTask.lift(fetch(postUrl(server, id)).map(x => x.data).map(JSON.parse))
  )

QUnit.test("Ex2", assert => {
	ex2(30)
	.run('http://serverA.com')
	.fork(
		e => console.error(e),
		posts => assert.deepEqual('Get some Fp', posts[0].title)
	)
})

Next exercise

4:48:01 - Functional Architecture Patterns on Livestream

  // Ex3: 
// =========================
const TaskEither = TaskT(Either)

const Api1 = ({
  getFavoriteId: user_id =>
	    Task((rej, res) =>
    	  res(user_id === 1 ? Right(2) : Left(null))
		),
	getPost: post_id =>
	    Task((rej, res) =>
    	  res(Either.fromNullable(posts[post_id-1]))
		)
})

const Api2 = ({
  getFavoriteId: user_id =>
	    TaskEither((rej, res) =>
    	  res(user_id === 1 ? Right(2) : Left(null))
		),
	getPost: post_id =>
	    TaskEither((rej, res) =>
    	  res(Either.fromNullable(posts[post_id-1]))
		)
})

// TODO: Rewrite ex3 using Api2
const ex3 = (user_id) =>
	Api1.getFavoriteId(user_id)
	.chain(epost_id =>
		epost_id
		.fold(
			() => Task.of(Left()),
			(post_id) => Api1.getPost(post_id)
		)
	)
	.map(epost =>
		 epost.map(post => post.title)
	)

const ex3 = (user_id) =>
	Api1.getFavoriteId(user_id)
	.chain(post_id => Api2.getPost(post_id))
	.map(post => post.title)

So Api2 is **much** nicer

QUnit.test("Ex3", assert => {
	ex3(1)
	.fork(
		e => console.error(e),
		ename =>
			ename.fold(
				error => assert.deepEqual('fail', error),
				name => assert.deepEqual('Learn to architect it', name)
			)
	)
})

Free monads

4:49:16 - Functional Architecture Patterns on Livestream

Usually they are not what you want, but they do solve a very sepecific problem.

(Reader or Dependency injection via .ask)

The free monad is a way to take your functions and treat them as data types.

An example of using free

  const { liftF } = require('../lib/free')
const { id } = require('../lib/types')

// for example:
httpGet = url => Task(..)

// instead we can return a data type
httpGet = url => HttpGet(url)

// and if this is a monad
// we can chain it as if it already did
// and just get the contents
HttpGet(url)
.chain(contents = HttpPost('/analytics', contents))

// you end up with a data structure representing the nested tree of computation
// you are creating an AST
// you are lifting your functions into data types
// lifting them as if they were running and interpret then later

// so we need to create an interpreter
// to interpret the above data structure (HttpGet.(...))
const { taggedSum } = require('daggy') // highly recommends this library
// it gives a shorter syntax to create the
// semigroup-like syntax we've been using
// HttpGet = x => ({x,...})

const interpret = () =>

More on daggy Also gives some functions to decompose several data types.

  // taggedSum is like a type definition

const Http = taggedSum('Http', {Get: ['url'], Post: ['url', 'body']})

We defined two data types, Get and Post

  console.log(Http.Get('/home')) // { url: '/home' }

So it's just a shorter syntax for the object literals from before. But it also gives a .cata catamorphism :

But it also gives a .cata catamorphism, aka pattern matching, and destructure the types and do actions on them:

  Get('/home').cata({
  Get: url => 'get',
  Post: (url, body) => 'post'
})

4:54:02 - Functional Architecture Patterns on Livestream Catamorphism helps you to simplify the checks, you don't have to write the if else statements to check if the data has certain property to know it's type.

With daggy, we made our function with .Get and .Post into a data type. And now we can add liftF to lift it into "free". 4:54:39 - Functional Architecture Patterns on Livestream

"free": free monad, just lifts the data into a type. Like the array [], it's a free monoid.

  //httpGet = url => liftF(url)
httpGet = url => liftF(Http.Get(url)) // Free(HttpGet)
httpPost = (url, body) => liftF(Http.Post(url, body)) // Free(HttpPost)

// now run the app
const app = () =>
  httpGet('/home') // we can .chain, (free monad above)
  .chain(contents => httpPost('./analytics', contents))

const res = app()
console.log(res)
// {x: {url: '/home'}, f: [Function]}
// `f` is our continuation function

Free monads are just a data type, but they normalise everything so we can .chain and .map etc.

Now, let's pass this to an interpreter Sidenote, he uses the Id when he's architecting a solution and when everything is in order swaps it out for a task.

  // Free(Stuff).foldMap() // and fold it to some other type, this is the interpreter


const interpret = x =>
  x.cata({
    Get: url => Id.of(`contents for ${url}`), // need to return a monad, Id
    Post: (url, body) => Id.of(`posted ${body} to ${url}`)
  })

const res = app().foldMap(interpret, Id.of)
console.log(res.extract())
// 'posted contents for /home to /analytics'

we've written our app with data, and in the end we decided which interpreter to use.

is catamorphism here a "fancy" switch or if else statement?

Yes.

Also daggy has some error checking and useful error messages when running the interpreter.

So this is the free monad. When it's hard to separate the logic from the side-effects (his example when they were doing an app that posted to npm, GitHub and all) How to test it, mock the api calls, and daggy and cata was a great solution for that.

Lenses

5:02:01 - Functional Architecture Patterns on Livestream

Lenses are both very simple and can be a very complex, deep subject. They are also very flexible. Lenses are built on functors and just like functors you can do anything with a combination of lenses and functors. You could rewrite every app by lenses.

In other languages (mentions Haskell) they more nuanced and so even more complex topic.

The lenses library from Ramda is pretty naive, there's a more hardcore lenses library called optica.

Here we will just focus on the top level lens concepts.

Here's the common pattern he uses, where he namespaces his lenses with L:

  const {toUpper, view, over, lensProp, compose} = require('ramda')

const L = {
  name: lensProp('name'),
  street: lensProp('street'),
  address: lensProp('address')
}

const user = {address: {street: {name: 'Maple'}}}

// to view a property
// lenses compose!
const res = view(compose(L.address, L.street, L.name), user) // again, lenses compose!
console.log(res) // Maple

We could jump into this deeply nested structure via lenses composition.

We can modify via over

  const addrStreetName = compose(L.address, L.street, L.name) 
const res = over(addrStreetName, toUpper, user)
console.log(res) // {address: {street: {name: 'MAPLE'}}}

Again, we could modify a deeply nested object, and get the whole thing back and also immutable. This can compose with monads:

  const user = Task.of({address: {street: {name: Either.of('Maple')}}})
const addrStreetName = compose(L.address, L.street, L.name, L.mapped)  // note the addition of L.mapped
const res = over(addrStreetName, toUpper, user)
console.log(res) // {address: {street: {name: 'MAPLE'}}}

This is very powerful. You can go into a deeply nested element, inside a monad, change a part and continue. Most people use profunctor lenses. Brian has a lens library on his GitHub.

Again, L.mapped is the "magic" that allows to interact with the monads.

It's like treating properties as functors.

Lens also work with lists, lensProp(0).

5:09:21 - Functional Architecture Patterns on Livestream Lenses preserve the structure of your data.

Lenses compose backwards, left to right (?) (see above the compose(...))

Building an app

A CLI blog

5:24:32 - Functional Architecture Patterns on Livestream Graph on the screen. Map of interactions. There's start from there you can either go to create author or straight to menu. From the menu we can see all authors, latest articles or write. The items have arrows pointing to the next item.

Will create the main architecture in a functional way.

The starting setup:

  const readline = require('readline').createInterface({input: process.stdin, output: process.stdout})

const getInput = q =>
   Task((rej, res)) => readline.question(q, i => res(i.trim()))

// it would work like this:
getInput('sup?').map(answer => answer.toUpperCase())
.fork(console.error, console.log)
// sup? dunno => DUNNO

Starts to stetch out the function and see their types. First, just to see what we may need.

  const {save, all, find} = reqire('../lib/db') // just some array based database, it wraps it in a Task

const AuthorTable = 'Authors'

const start = () => 
  // check if we have any authors
  all(AuthorTable) // this is a Task
  .map(authors => authors.length ? menu : createAuthor) // menu and createAuthor are not yet implemented

We go these few functions and then see what we need to refactor later... implemeting createAuthor and continueing

  // the Author type, see below
const Author = name => ({name})

const createAuthor = () =>
  getInput('Name? ')
  .map(name => Author(name)) // created an Author type
  .chain(author => save(AuthorTable, author)) // saving to the database, this is a Task
  .map(() => menu) // we're not handling the errors yet, later we can add our validation lib

He likes to create these data types, so that your data is defined and not just creating them ad hoc everywhere in your app.

Let's write menu

Note that we're just passing the continuation along

  
// () -> Task () -> Task () -> 
// some recoursive task
// but we can turn it into any monad:
// () -> m () -> m () ->
// and we take the unit:
// a -> m a -> m a ->
// we can regroup:
// a -> m (a -> m (a -> m))
// then, there's a type called Fix: 
// Fix f -> f (Fix f) // f is some functor, e.g. Task
// Fix Task -> Task (Fix Task)
// Fix is a promise that we will fix a "fix" point of this functor
// that we will keep recusing down until we hit a point
// but finally he will rule out Fix, because it must hold a traversable
// but Fix has a "close relative", called Free
// Free m -> Task (Free m) // monad
// it’s a travesible type
// Free m -> m (Free m) | Pure
// so the point is that we have a type that is compatible with the Free monad
const menu () => 
  getInput('where do you want to go today? (createAuthor, write, latest, all)')
  .map(route => router[route]) // we will need a router...

// () -> Task Task Task Task
// a recursive data structure of a Task holding a Task
const createAuthor = () =>
  ...

// also create the router
const router = {menu, createAuthor} // will add more later

now add the runner

  const runApp = () =>
  f().fork(console.error, runApp) // we have a loop

// the way we want to run it
runApp(start)
// Name?

5:39:18 - Functional Architecture Patterns on Livestream The "app" is now working. You can give a name when asked and move to the next step. Let's continue:

  

const AuthorTable = 'Authors'
// **** 3. add the posttable table :
const PostTable = 'Post'
const Author = name => ({name})
const Post = title,body => ({title,body})

const menu () => 
  getInput('where do you want to go today? (createAuthor, write, latest, all)')
  .map(route => router[route]) // we will need a router...

const createAuthor = () =>
  ...



// **** 5. add post formatting :
// will move this elsewhere later
// it doesn't really belong here
// will copy/paste (cut/paste) later it's easy
const formatPost = post => 
  `${post.title}:\n ${post.body}`


// **** 5. then print :
const print = s => Task((_rej, res) => res(console.log(s)))

// **** 4. add the latest listing :
const latest = () =>
  all(PostTable)
  .map(posts => last(posts)) // `last` is from Ramda
  .map(formatPost)
  .chain(print)
  .map(() => menu) // go back to menu when print is done

// **** 2. create write :
const write = () =>
  getInput('Title: ')
  .chain(title =>  // then ask for the body 
    getInput('Body: ')
    .map(body => Post(title, body)) // put it into a data type (don't create new ones on the fly!)
  ) 
  .chain(post => save(PostTable, post))
  .map(() => latest)

// **** 1. update route :
const router = {menu, createAuthor, write, latest} 

const runApp = () =>
  f().fork(console.error, runApp)

runApp(start)

Now it works, can tell name, write post, get the latest.

How do you test this?

These are all just tasks and writing to stdout (that's not so great).

Free monads were a great solution for this app, we could treat the interactions as data structures and interpret them with whatever interpreter we want. This could be a test interpreter or a live console print.

Free monads solution

5:47:37 - Functional Architecture Patterns on Livestream So far, everything in our app is Task. It would be better if we had specific actions, like httpGet before. And we would have data types that represent actions.

  
// **** 1. bring in lift
const { liftF } = reqire('../lib/free') // lets turn "things" into free monads
// **** 2. add daggy
const {taggedSum} = reqire('daggy')

// **** 3. create a type to read/write to console
// this will be interpreted in different ways
// the "names" (`['q']` below), don't really matter, only for inspection
// at work he would write the names all out...
const Console = taggedSum('Console', {Question: ['q'], Print:['s']})
const Db = taggedSum('Db', {Save: ['table', 'record'], All:['table', 'query']})

// **** 11. use question instead of getInput
const menu () => 
  // getInput('where do you want to go today? (createAuthor, write, latest, all)')
  question('where do you want to go today? (createAuthor, write, latest, all)')
  .map(route => router[route]) // we will need a router...

// **** 4. change the print, question method
const print = s => liftF(Console.Print(s))
const question = s => liftF(Console.Question(s))

// will interpret these (above lines) with getInput etc
const realPrint = s => Task((_rej, res) => res(console.log(s)))
const writeOutput = s => Task((_rej, res) => res(console.log(s)))

// **** 5. now the the same for db and
const dbAll = (table, query) => liftF(Db.All(table, query))
const dbSave = (table, record) => liftF(Db.Save(table, record))
// so there's a lot of repetition, but it's OK...

// **** 6. and update write
const write = () =>
  question('Title: ')
  .chain(title =>
    question('Body :')
    .map(body => Post(title, body))
  )
  .chain(post => dbSave(PostTable, post))

const createAuthor = () =>
  question('Name? ')
  .map(name => Author(name)) 
  // update
  //.chain(author => save(AuthorTable, author)) 
  .chain(author => dbSave(AuthorTable, author)) 
  .map(() => menu) 


const start = () => 
  // all(AuthorTable) 
  dbAll(AuthorTable) 
  .map(authors => authors.length ? menu : createAuthor) 

// **** 9. add dbToTask
const dbToTask = x =>
  x.cata({ Save: save, All: all, })
  // save is holding the same arguments
  // the above is equivalent to this:
  x.cata({ Save: (table, record) => save(table, record), All: all, })
  // save(table, record) returns us a Task

// **** 10. 
const consoleToTask = x =>
  x.cata({ Question: getInput, Print: writeOutput, })
// what we do is mapping our data types holding our arguments
// into functions

// **** 8. write the interpreter
// composable interpreters (with coproducts)
// for now, he only has two effects so won't do it
const interpreter = x =>
// we don't know if it's a console or a db, we check:
// would be easier in a typed language or with a different solution
  x.table ? dbToTask(x) : consoleToTask(x)
  


// **** 7. we're now using a free monad
  const runApp = f => // now, `f` returns a free monad and not a task
  // we have to interpret it
  //  f().fork(console.error, runApp)
    f()
    .foldMap(interpret, Task.of)
    .fork(console.error, runApp)

Free monads are working.

6:00:40 - Functional Architecture Patterns on Livestream

We can add interpretTest

  
// **** 2.  add the functions
// we're returing an Id that is logging out 
const consoleToId = x =>
  x.cata({ 
    Question: q => Id.of(`answer to ${q}`),
    Print: s => Id.of(`writing the string of ${s}`), })
// **** 3. 
const dbToId = x =>
  x.cata({ 
  Save: (table, r) => Id.of(`saving to ${r} ${table}`),
    All: (table, q) => Id.of(`find all ${table} ${q}`), 
    })

const interpreter = x =>
  x.table ? dbToTask(x) : consoleToTask(x)

// **** 1. 
const interpretTest = x =>
  x.table ? dbToId(x) : consoleToId(x)
  

const runApp = f => 
  runApp(
    f()
    // **** 4.  use interpretTest
    .foldMap(interpretTest, Id.of).extract
    )

6:05:22 - Functional Architecture Patterns on Livestream The above code does not work actually... He would mock out

  const consoleToId = x =>
  x.cata({ 
    //Question: q => Id.of(`answer to ${q}`),
    Question: q => Id.of(answers[q]), // mock answers
    Print: s => Id.of(`writing the string of ${s}`), })

To stop a free monad

Note that in runApp we are recoursively calling it.

  const {liftF, Free, Pure} = require('../lib/free')

would use Pure to stop it. Talking about Fix

  Free(F(Free(F)), Pure())

Building a habit "todont" app

A more typical front-end application. A "to do" type of list of things you should stop doing. Habits that you want to stop.

6:17:09 - Functional Architecture Patterns on Livestream

Webpack dev server, puts out the standard todo app template.

Uses React and a standard html sructure:

  <body>
<div id="app"></div>
<script src="index.bundle.js"></script>
</body>

the ui.js is a React app holding a few functional components. The base:

  const renderApp = (state, dispatch) => {
  const app = <App state={state} dispatch={dispatch} />
  ReactDOM.render(app, document.getElementbyId('app'))
}
export {renderApp}

The dispatch function gets called with an event, similar to but not Redux. It's called with an event name and a payload. There's create, view, destroy event names.

  //..
<input 
  ....
  onKeyDown={e => e.key === 'Enter' ? dispatch('create', {name: e.currentTarget.value}): undefined}
/>

Starting "app":

  import { renderApp } from './ui'

renderApp({page: 'list', habits: []}, (action, payload) => {})

6:18:59 - Functional Architecture Patterns on Livestream

We will be modeling the reduction process, just like before with the functions.

Again, stating app code:

  const renderApp = (state, dispatch) => {
  const app = <App state={state} dispatch={dispatch} />
  ReactDOM.render(app, document.getElementbyId('app'))
}
export {renderApp}
  import { renderApp } from './ui'

// **** 1. 
const create = (state, habit) => 
  Object.assign({}, state, {habits: [habit]})

const state = {page: 'list', habits: []}

// **** 3. create an "even loop" to start things off
// and wrap render in it
const appLoop = state => 
  renderApp(state, (action, payload) => {
    // **** 2. create new state and also call render app
    return renderLoop(create(state, payload))
  })

// **** 4. and start the app
appLoop({page: 'list', habits: []})

Now each action (typing, deleting, listing) have these payloads, inside appLoop: 'create' {name: 'asdf'} 'destroy' {idx: 0}, 'view' {idx: 0}


Now continues with a router

  const renderApp = (state, dispatch) => {
  const app = <App state={state} dispatch={dispatch} />
  ReactDOM.render(app, document.getElementbyId('app'))
}
export {renderApp}
  import { renderApp } from './ui'
import {over, lensProp, remove, append} from 'ramda'

// **** 5. create the Lenst namespace
const L = { habits: lensProp('habits') }

const create = (state, habit) => 
// **** 4. Append to the habit list, via Lenses!!
//  Object.assign({}, state, {habits: [habit]})
  over(L.habits, append(habit), state)
  Object.assign({}, state, {habits: [habit]})

const state = {page: 'list', habits: []}

// **** 3. stub out the route functions
const destroy = (state, {idx}) => 
// **** 5. implement to remove only one habit
  //Object.assign({}, state, {habits: []})
  over(L.habits, remove(idx, 1), state)

const view = (state, {idx}) => 
  Object.assign({}, state, {page: 'show', index: idx})

// **** 2. implement the router
const route = {create, destroy, view}

const appLoop = state => 
  renderApp(state, (action, payload) => {
  // **** 1. add the router
  //  return renderLoop(create(state, payload))
    return appLoop(route[action](state, payload))
  })

appLoop({page: 'list', habits: []})

Now the app is working pretty great. 6:26:39 - Functional Architecture Patterns on Livestream But!

Each time we need to deal with the state and keep adding to it. Instead what if we could just return the state and it was merged for us? Right now, it's up to the developer to always make sure to merge in the state with the current one (see Object.assign above). It would be nicer, if we could just return a monoid:

  const view = (state, {idx}) => 
//  Object.assign({}, state, {page: 'show', index: idx})
  Merge({page: 'show', index: idx}) // Merge is a monoid!

// and also for the rest:
// though with the lenses it doesn't buy us much...
const create = (state, habit) => 
  Merge(over(L.habits, append(habit), state))


const destroy = (state, {idx}) => 
  Merge(over(L.habits, remove(idx, 1), state))

So let's implement the Merge monoid

  const Merge = x => ({
  x,
  concat: other =>
    Merge(Object.assign({}, x, other.x))
})

// Merge({a:1}).concat(Merge({a: 2})) // -> we want Merge({a:2})

And update our main app loop:

  const appLoop = state => 
  renderApp(state, (action, payload) => 
//    appLoop(route[action](state, payload)) // now these are returning us a Merge
    appLoop(Merge(state).concat(route[action](state, payload)).x) // the `.x` takes it out from the two Merge
  )

appLoop({page: 'list', habits: []})

6:30:44 - Functional Architecture Patterns on Livestream

It works, but... you have to call Merge everywhere. Let's factor this out.

  const view = (state, {idx}) => 
  // Merge({page: 'show', index: idx}) 
  ({page: 'show', index: idx}) 

const create = (state, habit) => 
//  Merge(over(L.habits, append(habit), state))
  over(L.habits, append(habit), state)


const destroy = (state, {idx}) => 
  // Merge(over(L.habits, remove(idx, 1), state))
  over(L.habits, remove(idx, 1), state)

// now we're just returning little bits of state
// and instead put them in the Merge in appLoop
const appLoop = state => 
  renderApp(state, (action, payload) => 
    // appLoop(Merge(state).concat(route[action](state, payload)).x) 
    appLoop(
    Merge(state)
    .concat(Merge(route[action](state, payload)))
    .x
    ) 
  )

appLoop({page: 'list', habits: []})

This gives a very simple, clean action handlers, each just concered with a small part of the tree.

Next, optimising around the state. Notice that we can flip the arguments around and also that in view state is no longer used.

6:31:47 - Functional Architecture Patterns on Livestream Instead we could ask for the state:

  // **** 3. 
//const view = (state, {idx}) => 
 // ({page: 'show', index: idx}) 
const view = ({idx}) => 
  ({page: 'show', index: idx}) 

const create = habit => 
// **** 1. "ask" for the state
ask.map(over(L.habits, append(habit)))
//const create = (state, habit) => 
 // over(L.habits, append(habit), state)

// **** 2. simplify the others as well
//const destroy = (state, {idx}) => 
//  over(L.habits, remove(idx, 1), state)
const destroy = ({idx}) => 
  ask.map(over(L.habits, remove(idx, 1)))

const appLoop = state => 
  renderApp(state, (action, payload) => 
    appLoop(
    Merge(state)
    .concat(Merge(route[action](state, payload)))
    .x
    ) 
  )

appLoop({page: 'list', habits: []})

Now create is no longer a reducer, just a normal function.

implementing the ask

  import {Fn} from '../lib/types'
const {ask} = Fn

// now, create, destroy, return the Fn type (ask.map...)
// do it for view:
const view = ({idx}) => 
  Fn.of({page: 'show', index: idx}) 

// we can now just call our function with the payload
const appLoop = state => 
  renderApp(state, (action, payload) => 
    appLoop(
    Merge(state)
    //.concat(Merge(route[action](state, payload)))
    .concat(Merge(route[action](payload).run(state))) // run it with state (.run(state)), and our function can choose to use state or not (`view`)
    .x
    ) 
  )

appLoop({page: 'list', habits: []})

Now we're able to "pull state out of nowhere".

We could now pull out appLoop into a separate module, apploog, pull create, destroy, view into "controllers". Move Merge into a library. We could separate all these out into separate files, but this is not the point here..

How to further improve our code?

Let's start by wrapping each our function with Fn:

  
// **** 2. add setShowPage
const setShowPage = Fn(() => Fn.of({page: 'show'}))

// **** 3.  rename `view` to `setIndex`
const setIndex = Fn(({idx}) => 
// **** 1. wrap it with Fn AND remove `page`
  //({page: 'show', index: idx}) )
  ({index: idx}) )

const create = Fn(habit => 
ask.map(over(L.habits, append(habit))))

// wrap it with Fn
const destroy = Fn(({idx}) => 
  ask.map(over(L.habits, remove(idx, 1))))

Now we can combine our functions:

  const view = setIndex.concat(setShowPage)

And run it like so:

  const appLoop = state => 
  renderApp(state, (action, payload) => 
    appLoop(
    Merge(state)
    //.concat(Merge(route[action](payload).run(state))) 
    .concat(Merge(route[action].run(payload).run(state))) 
    .x
    ) 
  )

So now view just combines a lot of smaller pieces instead of being one huge function.

6:40:25 - Functional Architecture Patterns on Livestream If destroy would return a Task:

  const destroy = FnTask(({idx}) => 
  ask.map(over(L.habits, remove(idx, 1)))
  ).chain(...)

or wrap with another function and ask for en environment

  const destroy = Fn(({idx}) => 
  ask(env => 
  ask.map(over(L.habits, remove(idx, 1)))
  )
  )
  ///
  concat(Merge(route[action].run(payload).run(state).run(env))) 

That's a wrap. This course should have given a starting point to see the tools available and you can dig deeper if you need to...

Typescript

There are a few people who are pushing TypeScript into functional programming that make JavaScript feel like Scala. He will post their Twitter handles. He was hesitating to start the class with TypeScript, it would help catch the bugs when you don't put the correct type in your functions. But TypeScript doesn't like nested generics Promise<Either<Task<..>>>. Here you need a lot of tricks. He does recommend it, but if you plan to go very far with it (TypeScript) you may start to consider using a typed functional language in the first place (Elm, PureScript, Reason).

6:44:41 - Functional Architecture Patterns on Livestream End of video