folktale / data.task

Migrating to https://github.com/origamitower/folktale
MIT License
425 stars 45 forks source link

Seems like the implementation of the ap is incorrect #42

Closed ivan-demchenko closed 6 years ago

ivan-demchenko commented 6 years ago

Hello!

I was playing around with the Task type and I have discovered that there's something wrong with the implementation of ap. Here is my snippet:

var Task = require('data.task');

var readToTask = (delay, resData) => new Task((rej, res) => isNaN(delay) ? res(resData) : setTimeout(res, delay, resData));

var lift2 = (f, m1, m2) => m1.ap(m2.map(f));

var join = s1 => s2 => s1 + ' : ' + s2;

var program = () =>
    lift2(join, readToTask(1000, 'm1'), readToTask(NaN, 'm2'))

var program2 = () =>
    readToTask(1000, 'm1').chain(p1 =>
        readToTask(NaN, 'm2').map(p2 =>
            join(p1)(p2)
        )
    )

var program3 = () =>
    readToTask(1000, 'm1').ap(readToTask(NaN, 'm2').map(join))

program().fork(console.error, console.log); // func is not a function
program2().fork(console.error, console.log); // works
program3().fork(console.error, console.log); // func is not a function

It could be that I am doing something wrong since there was a major release. I can't check it with 2.x versions as npm can't find any. Unfortunately, I haven't had a chance to dig into the implementation.

robotlolita commented 6 years ago

Ah, I can see why this would be confusing.

Fantasy Land 0.x had the interface:

interface Applicative<F>
  <A, B> ap(this: F<A => B>, F<A>): F<B>

So you'd write it using:

Task.of(a => b => a + ' : ' + b).ap(Task.of('x')).ap(Task.of('y'));

data.task only implements this interface, which was the only one at the time.

Later in the 0.x branch, Fantasy Land changed the type of the Applicative interface like this:

interface Applicative<F>
  <A, B> ap(this: F<A>, F<A => B>): F<B>  

Which gives you:

Task.of('x').ap(Task.of('y').ap(f))

This was a breaking change, but then Fantasy Land 1.x was released with prefixed names:

interface Applicative<F>
  <A, B> "fantasy-land/ap"(this: F<A>, F<A => B>): F<B>

The new Folktale Task implements both, so you have:

Task.ap :: <A, B>(this: Task<A => B>, Task<A>): Task<B>
Task['fantasy-land/ap'] :: <A, B>(this: Task<A>, Task<A => B>): Task<B>

This article describes how to migrate from data.task to the new folktale library http://folktale.origamitower.com/docs/v2.0.0/migrating/from-data.task/

ivan-demchenko commented 6 years ago

thank you @robotlolita I'll definitely give it a try!

ivan-demchenko commented 6 years ago

Well, I ended up using ramda's lift as it uses prefixed names. It works rather good! Thank you!