I'm wondering if it's possible to "widen" my ultimate Reader type when using sequenceT? This is possible when chaining operations sequentially using chainW etc., but it looks like when using sequenceT you're stuck with every item having to use the same Reader type. I'd like to do this so I'm able to parallelise execution of some tasks where appropriate, but still be able to use dependency injection via Reader.
Example:
import { sequenceT } from 'fp-ts/lib/Apply'
import { log } from 'fp-ts/lib/Console'
import { pipe } from 'fp-ts/lib/function'
import * as RT from 'fp-ts/ReaderTask'
interface Person {
name: string
}
const getMe = (name: string) => (deps: { api: any }) => async (): Promise<Person> => {
const person = {
name,
}
return person
}
const getMum = (child: Person) => (deps: { api2: any }) => async (): Promise<Person> => {
const person = {
name: child.name + "'s mum",
}
return person
}
const getDad = (child: Person) => (deps: { api2: any }) => async (): Promise<Person> => {
const person = {
name: child.name + "'s dad",
}
return person
}
const getFamily = (name: string) =>
pipe(
getMe(name),
RT.chainW(me =>
sequenceT(RT.readerTask)(
getMum(me),
getDad(me))
))
getFamily('John')({ api: 'x', api2: 'y' })().then(
([mum, dad]) => {
log(mum)()
log(dad)()
})
This compiles fine and outputs:
$ node dist/src/index.js
{ name: "John's mum" }
{ name: "John's dad" }
Now let's say getDad relies on a different api, say api3. If I update the code it no longer compiles, because getMum and getDad aren't using the same Reader type.
Example (does not compile):
import { sequenceT } from 'fp-ts/lib/Apply'
import { log } from 'fp-ts/lib/Console'
import { pipe } from 'fp-ts/lib/function'
import * as RT from 'fp-ts/ReaderTask'
interface Person {
name: string
}
const getMe = (name: string) => (deps: { api: any }) => async (): Promise<Person> => {
const person = {
name,
}
return person
}
const getMum = (child: Person) => (deps: { api2: any }) => async (): Promise<Person> => {
const person = {
name: child.name + "'s mum",
}
return person
}
const getDad = (child: Person) => (deps: { api3: any }) => async (): Promise<Person> => {
const person = {
name: child.name + "'s dad",
}
return person
}
const getFamily = (name: string) =>
pipe(
getMe(name),
RT.chainW(me =>
sequenceT(RT.readerTask)(
getMum(me), // compiler complains here
getDad(me))
))
getFamily('John')({ api: 'x', api2: 'y', api3: 'z' })().then( // compiler complains here, on api3
([mum, dad]) => {
log(mum)()
log(dad)()
})
I was actually trying this with StateReaderTaskEither but simplified it to use ReaderTask for this example - sequenceT exhibits the same restriction with that too however.
Any ideas how to solve?
This is exactly what Reader/ReaderTask/ReaderTaskEither.local
is for! I use this regularly. For example, if you are parallelizing HTTP calls to an API where some require an auth token + base URL, while others only require base URL (so some use interface Auth { token:string, baseUrl: string }
while others use interface NoAuth { baseUrl: string }
.
interface Apis {
java: JavaRepository,
db: DbRepository,
redis: RedisRepository,
}
interface DomainError {}
declare const javaApiCall: RTE<JavaRepository, DomainError, JavaResult>
declare const dbApiCall: RTE<DbRepository, DomainError, DbResult>
declare const redisApiCall: RTE<RedisRepository, DomainError, RedisResult>
declare const apis: Apis
const getJava = (apis:Apis) => apis.java
const getDb = (apis:Apis) => apis.db
const getRedis = (apis:Apis) => apis.redis
sequenceT(readerTaskEither)(
RTE.local(getJava)(javaApiCall),
RTE.local(getDb)(dbApiCall),
RTE.local(getRedix)(redisApiCall),
)(apis) // TaskEither<DomainError, [JavaResult,DbResult,RedisResult]>
I figured this one out after a little more reading of fp-ts code. The answer I came up with is to just do what sequenceT effectively does manually.
Here's my solution:
import { log } from 'fp-ts/lib/Console'
import { pipe } from 'fp-ts/lib/function'
import * as RT from 'fp-ts/ReaderTask'
interface Person {
name: string
}
const getMe = (name: string) => (deps: { api: any }) => async (): Promise<Person> => {
const person = {
name,
}
return person
}
const getMum = (child: Person) => (deps: { api2: any }) => async (): Promise<Person> => {
const person = {
name: child.name + "'s mum",
}
return person
}
const getDad = (child: Person) => (deps: { api3: any }) => async (): Promise<Person> => {
const person = {
name: child.name + "'s dad",
}
return person
}
const getFamily = (name: string) =>
pipe(
getMe(name),
RT.chainW(me =>
pipe(
RT.of((mum: Person) => (dad: Person) => [mum, dad]),
RT.apW(getMum(me)),
RT.apW(getDad(me))
)
))
getFamily('John')({ api: 'x', api2: 'y', api3: 'z' })().then(
([mum, dad]) => {
log(mum)()
log(dad)()
})
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.