MostlyAdequate / mostly-adequate-guide

Mostly adequate guide to FP (in javascript)
Other
23.41k stars 1.87k forks source link

Chapter 10: claim that applicative calls happen instantly #558

Open ahmetsevki opened 4 years ago

ahmetsevki commented 4 years ago

This statement:

// Http.get :: String -> Task Error HTML

const renderPage = curry((destinations, events) => { /* render page */ });

Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events'));
// Task("<div>some page with dest and events</div>")

Both Http calls will happen instantly and renderPage will be called when both are resolved. Contrast this with the monadic version where one Task must finish before the next fires off. Since we don't need the destinations to retrieve events, we are free from sequential evaluation.

I have been pondering if the following statement was true, and finally wrote a small test to see that it doesn't work this way (the calls are not made instantly, rather, they will be issued one after another. )

if you run this piece of code, you will see that all tasks are chained (the ap method is implemented with chain)

const _ = require('@mostly-adequate/support')

const makeTask = (name, v, timeout) => new _.Task( (rej, res) =>  {
  console.log(`begin - ${name} fork`)
  setTimeout(() => {
    console.log(`timeout - ${name}`)
    res(v)
  }, timeout )
  console.log(`end - ${name} fork`)
})
const add = _.curry( (x , y, z  ) => x + y + z)
const t1 = makeTask('t1', 1, 1000);
const t2 = makeTask('t2', 2,3000);
const t3 = makeTask('t3', 3, 5000);

const add1  = t1.map(add)
const taskAp = _.Task.of( add ).ap(t1).ap(t2).ap(t3)

const throwUnexpected = () => { throw new Error('')};
taskAp.fork( 
  throwUnexpected,
  (resolve) => { console.log(`result`, resolve)}
)

Am I not following something correctly? It behaves the same way if I wrote it this way:

const taskMonad = _.Task.of(add)
  .chain(fn => t1.map(fn))
  .chain(fn => t2.map(fn))
  .chain(fn => t3.map(fn))

And it is natural because, this is the implementation of ap in Task:

// ----- Applicative (Task a)
  ap(f) {
    return this.chain(fn => f.map(fn));
  }

  // ----- Monad (Task a)
  chain(fn) {
    return new Task((reject_, resolve) => this.fork(reject_, x => fn(x).fork(reject_, resolve)));
  }
ahmetsevki commented 4 years ago

Oh by the way, this is fantastic piece of work, I am recommending this to everyone around me.