Статья: https://habr.com/ru/post/548622
Created
March 23, 2021 19:53
-
-
Save YBogomolov/bf1c49e47aa579dac97d03da847b0d36 to your computer and use it in GitHub Desktop.
Статья про Task, TaskEither, ReaderTaskEither
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Task — ленивый примитив асинхронных вычислений | |
type Task<A> = () => Promise<A>; | |
// Уникальный идентификатор ресурса — тэг типа (type tag) | |
const URI = 'Task'; | |
type URI = typeof URI; | |
// Определение Task как типа высшего порядка (higher-kinded type) | |
declare module 'fp-ts/HKT' { | |
interface URItoKind<A> { | |
[URI]: Task<A>; | |
} | |
} | |
const Functor: Functor1<URI> = { | |
URI, | |
map: <A, B>(taskA: Task<A>, transform: (a: A) => B): Task<B> => async () => { | |
const prevResult = await taskA(); | |
return transform(prevResult); | |
}, | |
}; | |
const Apply: Apply1<URI> = { | |
...Functor, | |
ap: <A, B>(taskA2B: Task<(a: A) => B>, taskA: Task<A>): Task<B> => async () => { | |
const transformer = await taskA2B(); | |
const prevResult = await taskA(); | |
return transformer(prevResult); | |
}, | |
}; | |
const ApplyPar: Apply1<URI> = { | |
...Functor, | |
ap: <A, B>(taskA2B: Task<(a: A) => B>, taskA: Task<A>): Task<B> => async () => { | |
const [transformer, prevResult] = await Promise.all([taskA2B(), taskA()]); | |
return transformer(prevResult); | |
}, | |
}; | |
const Applicative: Applicative1<URI> = { | |
...Apply, | |
of: <A>(a: A): Task<A> => async () => a, | |
}; | |
const Monad: Monad1<URI> = { | |
...Applicative, | |
chain: <A, B>(taskA: Task<A>, next: (a: A) => Task<B>): Task<B> => async () => { | |
const prevResult = await taskA(); | |
const nextTask = next(prevResult); | |
return nextTask(); | |
}, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as fs from 'fs'; | |
import { pipe } from 'fp-ts/function'; | |
import * as Console from 'fp-ts/Console'; | |
import * as TE from 'fp-ts/TaskEither'; | |
// Сначала я оберну функции из системного модуля `fs` при помощи `taskify`, сделав их чистыми: | |
const readFile = TE.taskify(fs.readFile); | |
const writeFile = TE.taskify(fs.writeFile); | |
const program = pipe( | |
// Входная точка — массив задач по чтению трёх файлов с диска: | |
[readFile('/tmp/file1'), readFile('/tmp/file2'), readFile('/tmp/file3')], | |
// Для текущей задачи важен порядок обхода массива, поэтому я использую последовательную, а не параллельную версию | |
// функции traverseArray – `traverseSeqArray`: | |
TE.traverseSeqArray(TE.map(buffer => buffer.toString('utf8'))), | |
// При помощи функции `chain` из интерфейса монады я организую последовательность вычислений: | |
TE.chain(fileContents => writeFile('/tmp/combined-file', fileContents.join('\n\n'))), | |
// Наконец, в финале я хочу узнать, завершилась ли программа успешно или ошибочно, и залогировать это. | |
// Тут мне поможет модуль `fp-ts/Console`, содержащий чистые функции по работе с консолью: | |
TE.match( | |
err => TE.fromIO(Console.error(`An error happened: ${err.message}`)), | |
() => TE.fromIO(Console.log('Successfully written to the combined file')), | |
) | |
); | |
// Наконец, запускаем нашу чистую программу на выполнение, выполняя все побочные эффекты: | |
await program(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Reader это функция из некоторого окружения типа `E` в значение типа `A`: | |
type Reader<E, A> = (env: E) => A; | |
// Reader является типом высшего порядка, поэтому определим всё необходимое: | |
const URI = 'Reader'; | |
type URI = typeof URI; | |
declare module 'fp-ts/HKT' { | |
interface URItoKind2<E, A> { | |
readonly Reader: Reader<E, A>; | |
} | |
} | |
// Функтор: | |
const readerFunctor: Functor2<URI> = { | |
URI, | |
map: <R, A, B>(fa: Reader<R, A>, f: (a: A) => B): Reader<R, B> => (env) => f(fa(env)) | |
}; | |
// Apply: | |
const readerApply: Apply2<URI> = { | |
...readerFunctor, | |
ap: <R, A, B>(fab: Reader<R, (a: A) => B>, fa: Reader<R, A>): Reader<R, B> => (env) => { | |
const fn = fab(env); | |
const a = fa(env); | |
return fn(a); | |
} | |
}; | |
// Аппликативный функтор: | |
const readerApplicative: Applicative2<URI> = { | |
...readerApply, | |
of: <R, A>(a: A): Reader<R, A> => (_) => a | |
}; | |
// Монада: | |
const readerMonad: Monad2<URI> = { | |
...readerApplicative, | |
chain: <R, A, B>(fa: Reader<R, A>, afb: (a: A) => Reader<R, B>): Reader<R, B> => (env) => { | |
const a = fa(env); | |
const fb = afb(a); | |
return fb(env); | |
}, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { sequenceS } from 'fp-ts/Apply'; | |
import { pipe } from 'fp-ts/function'; | |
import * as R from 'fp-ts/Reader'; | |
import Reader = R.Reader; | |
const seq = sequenceS(R.Apply); | |
interface AppConfig { | |
readonly host: string; | |
readonly port: number; | |
readonly connectionString: string; | |
} | |
type Database = 'connected to the db'; | |
type Express = 'express is listening'; | |
type App<A> = Reader<AppConfig, A>; | |
const expressServer: App<Express> = pipe( | |
R.ask<AppConfig>(), | |
R.map( | |
config => { | |
console.log(`${config.host}:${config.port}`); | |
return 'express is listening'; | |
}, | |
), | |
); | |
const databaseConnection: App<Database> = pipe( | |
R.asks<AppConfig, string>(cfg => cfg.connectionString), | |
R.map( | |
connectionString => { | |
console.log(connectionString); | |
return 'connected to the db'; | |
}, | |
), | |
); | |
const application: App<void> = pipe( | |
seq({ | |
db: databaseConnection, | |
express: expressServer, | |
}), | |
R.map( | |
({ db, express }) => { | |
console.log([db, express].join()); | |
console.log('app was initialized'); | |
return; | |
}, | |
), | |
); | |
application({ | |
host: 'localhost', | |
port: 8080, | |
connectionString: 'mongo://localhost:271017', | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment