Как использовать монаду задач? (fp-ts)

#typescript #functional-programming #fp-ts

#typescript #функциональное программирование #fp-ts

Вопрос:

 import * as T from 'fp-ts/lib/Task'
import { pipe, flow } from 'fp-ts/lib/function'

const getHello: T.Task<string> = () => new Promise((resolve) => {
  resolve('hello')
})
 

Я понимаю цель Task и почему это важно. Дело в том, что я не знаю, как правильно его использовать или составлять с ним, на самом деле.

Если я просто позвоню getHello() , это даст мне Promise<pending> :

 console.log(getHello()) // returns Promise<pending>
 

однако, если я это сделаю:

 const run = async () => {
  const hello = await getHello()
  console.log(hello) // prints 'hello'
}
 

это работает.

но это:

 const waitAndGet = async () => {
  return await getHello()
}

console.log(waitAndGet()) // prints Promise<pending>
 

не работает.

Более того, как я смогу с ним работать? Вот так:

 const getHelloAndAddWorld = flow(
  getHello(),
  addAtEnd('world')
)
 

Комментарии:

1. async функции всегда возвращают обещания, это не имеет ничего общего с fp-ts. Task похоже, это просто псевдоним для чего-то вызываемого, который возвращает обещание, которым async также будет функция.

Ответ №1:

Сначала давайте разберемся, что Task есть на самом деле.

 export interface Task<A> {
  (): Promise<A>
}

// note that this could also be written as
export type Task<A> = () => Promise<A>
 

A Task — это просто функция, которая возвращает a Promise , поэтому в вашем примере вызов getHello вернет a Promise<string> .

console.log(getHello()) это то же console.log(Promise.resolve('hello')) самое, что и , поэтому он будет регистрировать что-то вроде Promise {<fulfilled>: "hello"} , Promise<pending> , или что-то еще вместо hello :

 // Promise.resolve(foo) is the same as new Promise(resolve => resolve(foo))
const getHello = () => Promise.resolve('hello')
console.log(getHello()) 

Для получения дополнительной информации об обещаниях я рекомендую прочитать Использование обещаний в MDN.


Что касается того, как с ним работать, поскольку Task Monad вы можете использовать map , ap , chain , apSecond и т. Д.

Например, допустим addAtEnd , был определен следующим образом:

 const addAtEnd = (b: string) => (a: string): string => a   b
 

Вы можете использовать это с getHello() помощью Task.map :

 import * as T from 'fp-ts/Task'
import { pipe } from 'fp-ts/function'

// type of map:
// export declare const map: <A, B>(f: (a: A) => B) => (fa: Task<A>) => Task<B>

// Task<string> which, when called, would resolve to 'hello world'
const getHelloAndAddWorld = pipe(
  getHello,
  T.map(addAtEnd(' world'))
)

// same as
const getHelloAndAddWorld = T.map(addAtEnd(' world'))(getHello)
 

Или, если вы хотите записать значение этого, вы могли бы использовать chainIOK и Console.log :

 import * as Console from 'fp-ts/Console'

// type of T.chainIOK:
// export declare function chainIOK<A, B>(f: (a: A) => IO<B>): (ma: Task<A>) => Task<B>

// type of Console.log:
// export declare function log(s: unknown): IO<void>

// Note that IO<A> is a function that (usually) does a side-effect and returns A
// (() => A)

// Task<void>
const logHelloAndWorld = pipe(
  getHelloAndAddWorld,
  T.chainIOK(Console.log)
)

// same as
const logHelloAndWorld = pipe(
  getHello,
  T.map(addAtEnd(' world')),
  T.chainIOK(Console.log)
)
 

Чтобы выполнить Task s, просто вызовите его:

 logHelloAndWorld() // logs 'hello world'
 

Для простого ознакомления с функторами, аппликативами и монадами, хорошей отправной точкой являются «Функторы, аппликативы и монады в картинках» Adit или их версия для JavaScript от Tze-Hsiang Lin.

Комментарии:

1. большое спасибо, что нашли время объяснить мне это, это очень много значит! теперь я понимаю это лучше. Task map принимает чистую программу и применяется к Task ней еще до того, как она будет разрешена с помощью . then я знаю, что это другой вопрос, и мне жаль, но я попытался сделать то же самое, что и вы, и это выдает мне ошибку. кроме того, знаете ли вы, есть ли какой-либо способ извлечь значение из задачи? если бы вы могли разобраться в этом: codesandbox.io/s/young-field-lnzi2?file=/src/index.ts

2. или, другими словами, как мне выполнить его после его составления?

3. @padowbr Извините, я забыл добавить такую простую вещь! Чтобы выполнить a Task , просто вызовите его: logHelloAndWorld() . То же самое и для IO s.

4. И извините за ошибку, моя ошибка. Я исправил это в своем ответе; это должно быть getHello , а не getHello() .

5. все в порядке! большое вам спасибо. но что, если вместо того, чтобы записывать разрешенное значение в консоль, я хочу вернуть само значение?