简体   繁体   English

使用 fp-ts sequenceT 时如何“加宽”阅读器类型?

[英]How to "widen" reader type when using fp-ts sequenceT?

I'm wondering if it's possible to "widen" my ultimate Reader type when using sequenceT?我想知道在使用 sequenceT 时是否可以“扩大”我的最终 Reader 类型? 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.这在使用 chainW 等按顺序链接操作时是可能的,但看起来在使用 sequenceT 时,您必须使用相同的 Reader 类型的每个项目。 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.我想这样做,以便我能够在适当的情况下并行执行某些任务,但仍然能够通过 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.现在假设 getDad 依赖于不同的 api,比如 api3。 If I update the code it no longer compiles, because getMum and getDad aren't using the same Reader type.如果我更新它不再编译的代码,因为 getMum 和 getDad 没有使用相同的 Reader 类型。

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.我实际上是用 StateReaderTaskEither 尝试这个,但在这个例子中简化了它以使用 ReaderTask - 然而,sequenceT 也表现出相同的限制。

Any ideas how to solve?任何想法如何解决?

This is exactly what Reader/ReaderTask/ReaderTaskEither.local is for!这正是Reader/ReaderTask/ReaderTaskEither.local的用途! 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 } .例如,如果您将 HTTP 调用并行化到 API,其中一些需要身份验证令牌 + 基本 URL,而其他人只需要基本 URL(因此有些使用interface Auth { token:string, baseUrl: string }而其他使用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.在阅读了一些 fp-ts 代码后,我发现了这一点。 The answer I came up with is to just do what sequenceT effectively does manually.我想出的答案是只做sequenceT手动有效地做的事情。

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)()
  })

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM