自然变换 - Natural Transformation

May 01, 2020 by sylvenas

自然变换

所谓自然变换也也就是变化包裹值的容器盒子:F(x) => G(x),举个简单的例子,把Either转换为Task:

const eitherToTask = e =>
    e.fold(Task.rejected, Task.of)

const res = eitherToTask(Right('hello'))
    .fork(err => { console.error('err', err) }, x => console.log('res', x)) // res hello

const res2 = eitherToTask(Left('errrrrr'))
    .fork(err => { console.error('err', err) }, x => console.log('res', x)) // err errrrrr

同构

前面我们介绍过Task 是一种Lazy Promise的概念,那么是否可以将Task转为promise呢?答案是肯定的!

 // taskToPromise :: Task a b -> Promise a b
const taskToPromise = x => new Promise((resolve, reject) => x.fork(reject, resolve));
const task = Task((rej, res) => {
    setTimeout(() => res('hello taskToPromise'), 200)
})

const res = taskToPromise(task)
    .then(x => { console.log(x) }, x => console.log('something went wrong')) // => hello taskToPromise

console.log(res) // => Promise { <pending> }

同样的,我们也可以把Promise转换为Task

const promiseToTask = p => Task((reject, resolve) => p.then(resolve).catch(reject));

const promise = Promise.resolve('hello promiseToTask')

const res = promiseToTask(promise)
    .fork(
        err => console.log('something went wrong'),
        x => console.log(x)) // => hello promiseToTask

**Note:**我们没办法实现taskToEither,因为我们不能把一个异步的逻辑转换为同步的过程,这个是不合理的,因为异步的结果,必须要等到异步call back的时候才能拿到。

定律

nth(fx).map(f) == nt(fx.map(f))

也就是先进行map然后自然变换和先自然变化然后map的结果是一样的

    // 因为nt必须满足这个定律所以boxToEither必须使用Right,因为left会跳过map
    const boxToEither = b =>
        b.fold(Right)

    const res = boxToEither(Box(200)).map(x => x * 2)
    console.log(res) // Right(400)

    const res2 = boxToEither(Box(200).map(x => x * 2))
    console.log(res2) // Right(400)

自然变换的目的

是为了函数组合,其实我们目前所做的所有努力都是为了让函数组合更方便,是想如果自然变换,我们怎么进行不同容器的chain和map呢?

const fake = id =>
    ({ id, name: `user${id}`, best_friend_id: id + 1 })

const Db = ({
    find: id =>
        Task((rej, res) =>
        res(id > 2 ? Right(fake(id)) : Left('not found')))
    })

const eitherToTask = e =>
    e.fold(Task.rejected, Task.of)

const app = id => Db.find(id) // Task(Right(user))
    .chain(eitherToTask)
    .chain(user => Db.find(user.best_friend_id))
    .chain(eitherToTask)

app(3).fork(console.error, console.log) // { id: 4, name: 'user4', best_friend_id: 5 }
app(2).fork(console.error, console.log) //not found