【问题标题】:How to "widen" reader type when using fp-ts sequenceT?使用 fp-ts sequenceT 时如何“加宽”阅读器类型?
【发布时间】:2020-11-23 23:21:53
【问题描述】:

我想知道在使用 sequenceT 时是否可以“扩大”我的终极阅读器类型?这在使用 chainW 等按顺序链接操作时是可能的,但看起来在使用 sequenceT 时,您会遇到每个项目都必须使用相同的 Reader 类型。我想这样做,这样我就可以在适当的地方并行执行一些任务,但仍然可以通过 Reader 使用依赖注入。

例子:

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

这可以很好地编译并输出:

$ node dist/src/index.js
{ name: "John's mum" }
{ name: "John's dad" }

现在假设 getDad 依赖于不同的 api,比如 api3。如果我更新它不再编译的代码,因为 getMum 和 getDad 没有使用相同的 Reader 类型。

示例(不编译):

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

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

有什么解决办法吗?

【问题讨论】:

    标签: typescript functional-programming fp-ts


    【解决方案1】:

    这正是Reader/ReaderTask/ReaderTaskEither.local 的用途!我经常使用这个。例如,如果您正在并行化对 API 的 HTTP 调用,其中一些需要身份验证令牌 + 基本 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]>
    

    【讨论】:

    • 更好!感谢您的强调。 Fp-ts 很棒,但它有点像寻宝游戏,缺乏实用的文档。
    • fp-ts 当然假设人们对 FP 的熟悉程度在他们第一次接触它时可能没有。我很幸运,我在 Kotlin 中使用 Arrow 在 FP 中崭露头角,所以我熟悉读者的 local 之类的东西以及它的用途。我记得当我第一次在 Kotlin 中使用它时,直到有一天我看到它被使用时才知道它到底是干什么用的。
    【解决方案2】:

    在阅读更多 fp-ts 代码后,我发现了这一点。我想出的答案是手动执行 sequenceT 有效执行的操作。

    这是我的解决方案:

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

    【讨论】:

      猜你喜欢
      • 2021-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-17
      • 1970-01-01
      • 2022-07-19
      • 2021-08-01
      相关资源
      最近更新 更多