MostlyAdequate / mostly-adequate-guide

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

CH10 How can 2 aps start instantly? #571

Open admhemed opened 4 years ago

admhemed commented 4 years ago
// 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. Does this mean the 2 http requests will start asynchronously? But we know that

Container.prototype.ap = function (otherContainer) {
return otherContainer.map(this.$value);
};

Which means in the first ap implementation the map ( Http.get('/destinations').map) will not be called until the task is done and resolved. Right? So, the second ap (and its http request) will start only after the first http request is done. Do you agree with me?

Another point, if the 2 http requests start instantly, second one may resolve before the first one, and we know renderPage is curried, so how can the second ap use it with its second argument while the first argument is still not available?

Sylvenas commented 4 years ago

I also encountered the same problem,example code :

// Task(functor,applicative,monad)
const Task = fork => ({
  map: f => Task((reject, resolve) =>
    fork(reject, a => resolve(f(a)))),
  ap: fn =>
    Task((reject, resolve) => fork(reject, a =>
      fn.map(a).fork(reject, resolve)
    )),
  chain: f =>
    Task((reject, resolve) => fork(reject, a =>
      f(a).fork(reject, resolve))),
  fork,

  [Symbol.for('nodejs.util.inspect.custom')]: () => 'Task(?)'
})
// lift
Task.of = a => Task((_, resolve) => resolve(a))

// --------------------------async------------

// async get user name
const fetchName = Task((_, resolve) => {
  setTimeout(() => {
    resolve('Melo')
  }, 2000)
});

// async get user age
const fetchAge = Task((_, resolve) => {
  setTimeout(() => {
    resolve(24)
  }, 2000)
});

// pure app
const app = Task
  .of(name => age => ({ name, age }))
  .ap(fetchName)
  .ap(fetchAge)

// effect
app.fork(() => {
  console.log('something went wrong')
}, x => {
  console.log('x', x)  // 4 seconds later log {name:'Melo', age:24} 
})

“It should be pointed out that part of ap's appeal is the ability to run things concurrently so defining it via chain is missing out on that optimization. Despite that, it's good to have an immediate working interface while one works out the best possible implementation.”

Quote from mostly-adequate-guide

But, the example code take 4 (fetchName 2 + fetchAge 2) seconds !!

How can I call fetchName and fetchAge in parallel with the applicative in Task.

Can someone help me rewrite the ap method ?

dotnetCarpenter commented 3 years ago

Thanks @admhemed and @Sylvenas for bringing this up. I spend hours trying to figure this out. Implemented HTML pages, with real HTTP calls to mocked up JSON data etc. I thought I was mad. But turns out that it's not possible with the implementation given in this book.

I forgot along the way that it was pointed out somewhere..

“It should be pointed out that part of ap's appeal is the ability to run things concurrently so defining it via chain is missing out on that optimization. Despite that, it's good to have an immediate working interface while one works out the best possible implementation.”

Sylvenas commented 3 years ago

Hi,@admhemed & @dotnetCarpenter ,similar to Promise.all, but the ap function only needs to receive one Task.

// Task(functor,applicative,monad)
const Task = fork => ({
  map: f => Task((reject, resolve) =>
    fork(reject, a => resolve(f(a)))),
  ap: fn => Task((reject, resolve) => {
          let func, rejected
          const firstState = fork(x => {
              rejected = true;
              return reject(x)
          }, x => func = x)
          const senondState = fn.fork(x => {
              rejected = true;
              return reject(x)
          }, x => {
              if (rejected) return
              return resolve(func(x))
          })
          return [firstState, senondState]
      }),
  chain: f =>
    Task((reject, resolve) => fork(reject, a =>
      f(a).fork(reject, resolve))),
  fork,

  [Symbol.for('nodejs.util.inspect.custom')]: () => 'Task(?)'
})
// lift
Task.of = a => Task((_, resolve) => resolve(a))

The implementation of Task is not suitable for the book, but it can be used as an appendix, I think this can help some readers.