Skip to content

Instantly share code, notes, and snippets.

@Sylvenas
Last active Dec 29, 2020
Embed
What would you like to do?
Functional programming Task async in parallel
// 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}
})
@Sylvenas
Copy link
Author

Sylvenas commented May 22, 2020

“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) second !!

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

Any idea or example code! thanks!

@ivenmarquardt
Copy link

ivenmarquardt commented May 22, 2020

I will implement an applicative instance for my Parallel type soon. For the time being there is already a race monoid. Check it out in my FP course chapter about monoids.

Please note that Task shouldn't implement an applicative with in parallel semantics, because monad and applicative should always act equally.

@Sylvenas
Copy link
Author

Sylvenas commented May 25, 2020

I think the chain method of monad is more suitable for chain calling, for example:

const fetchName = (name) => Task((_, resolve) => {
  setTimeout(() => {
    resolve(name)
  }, 2000)
});

const fetchAgeByName = (name) => Task((_, resolve) => {
  setTimeout(() => {
    resolve(name == 'melo' ? 24 : 26)
  }, 2000)
});

const app = fetchName('melo').chain(fetchAgeByName)

app.fork(() => {
  console.log('something went wrong')
}, age => {
  console.log('age', age) // age 24
})

Applicative(ap) is like Promise.all and Monad(chain) is like Promise.then ,I understand it this way, I don’t know it’s correct ?

@ivenmarquardt
Copy link

ivenmarquardt commented May 25, 2020

Yes, Promise.then is almost chain, because Promise is almost a monad. But there is the policy in the functional (Haskell) community that demands ap must have the same semantics as chain, that is, both must be evaluated in-sequence. For that reason there are two distinct types for async computations, a monadic type with in-sequence semantics, which has an applicative instance with the same semantics. And an applicative type with in-parallel semantics, which has no monad instance, because monads cannot be evaluated in-parallel by design.

However, it is just a policy to make async code more readable. You can mix both types if you want to. Here is a brief dev.to post I wrote about this topic.

@Sylvenas
Copy link
Author

Sylvenas commented May 27, 2020

Great post! Thanks.

Do you have a plan to put all posts on github? I can do the translation (chinese) work.

@ivenmarquardt
Copy link

ivenmarquardt commented May 27, 2020

I will actually need a Mandarin translation for my course on Github at some point. However, for the time being everything is still in flux, chapters are revised. Translating it makes only sense when things get more stable.

@Sylvenas
Copy link
Author

Sylvenas commented Dec 29, 2020

Similar to Promise.all. But the ap function only needs to receive one Task.

const Task = fork => ({
    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]
      })
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment