【问题标题】:Represent Task[Either] and IO[Either] as a single monad that includes tryCatch?将 Task[Either] 和 IO[Either] 表示为包含 tryCatch 的单个 monad?
【发布时间】:2021-03-14 16:44:55
【问题描述】:

有没有办法将 IOEither 和 TaskEither 表示为单个 Monad,其中还包括 tryCatch

我目前将通过 HTTP 使用 API,因此使用 TaskEither 是有意义的,但预计此代码将“离家更近”迁移,此时将其设为 IOEither 是有意义的。所以我想写一个无标签风格的消费者界面

interface EngineRepository<M extends URIS2> {
  calculateNumber: (i:SomeData) => Kind2<M, DomainError, number>
}

const getRepo = <M>(m:M extends URIS2):EngineRepository<M> => ({
  calculateNumber: someCalculation(m)()
})

const calculateNumber = <M>(m:M extends URIS2) => flow(/* M.chain, M.map, etc. works great! */)

到目前为止一切顺利!但是,虽然 Option、Either、TaskEither、IOEither 等有一个 tryCatch,但我可以说它不是任何接口的一部分。所以我正在尝试创建自己的:

interface Tryable<M extends URIS2> extends Monad2<M> {
  tryCatch: <E,A>(f:Lazy<A>, onError: (reason:unknown) => E) => Kind2<M, E, A>
}

const calculateNumber = <M>(m:M extends URIS2) => 
  flow(/* M.tryCatch works great now! */)

这里的问题是IOError 是同步的,所以f:Lazy&lt;A&gt; 很好,TaskEither 是异步的,所以它需要改为f:Lazy&lt;Promise&lt;A&gt;&gt;

有没有更好的方法来解决这个问题,或者这是不可能的?我是否需要始终使用 TaskEither,然后添加一个将 IOEither 转换为 TaskEither 并放弃无标记 final 的步骤?

【问题讨论】:

  • “离家更近”意味着目前我正在使用一个不同 Java 微服务的 API(所以它几乎不是“微”哈哈!),但我认为另一个团队正在将它移植到 TypeScript 和将它添加到这个应用程序的代码库中,所以IOEither
  • fp-ts - async/sync中TaskIO有什么区别?
  • @scriptum 是的。 interface Task&lt;A&gt; { ():Promise&lt;A&gt; }interface IO&lt;A&gt; { ():A }
  • (以及扩展名TaskEither&lt;E,A&gt; { ():Promise&lt;Either&lt;E,A&gt;&gt; }IOEither&lt;E,A&gt; { ():Either&lt;E,A&gt; }
  • 我不知道 fp-ts,但是一个更通用的 monad 可以处理异步(任务)和延迟/延迟同步(IO)计算并具有副作用是 continuation monad,巧合的是,它也是一个 monad变压器,因此您可以将其与您选择的异常处理机械地结合起来。

标签: typescript functional-programming fp-ts tagless-final


【解决方案1】:

我有一个临时的解决方案,感觉很老套:

interface Tryable<M extends URIS2, ThunkType extends 'Task'|'IO'> extends MonadIO2<M> {
  tryCatch: <E, A>(f:Kind<ThunkType, A>, onError: (e:unknown)=>E) => Kind2<M, E, A>
}

const te: Tryable<'TaskEither', 'Task'> = { ...TE.taskEither, tryCatch: TE.tryCatch}
const ioe: Tryable<'IOEither', 'IO'> = { ...IOE.ioEither, tryCatch: IOE.tryCatch}

pipe(te.tryCatch(()=>Promise.resolve(5),()=>'error'), te.map(num=>`${num}`)) // TaskEither<'error',string>

pipe(ioe.tryCatch(()=>5,()=>'error'), ioe.map(num => `${num}`)) // IOEither<'error',string>

它有效,但我不喜欢它结合 monad 和 thunk 类型,原因有两个:

  1. 如果我们为Option 实现Tryable,则thunk 类型仍然是IOEither 和其他人也一样。而是它开始感到hacky的地方,将OptionEitherIO紧密耦合!

  2. 这只是一个幸运的巧合,感谢鸭子输入Lazy&lt;A&gt;IO&lt;A&gt; 的类型相同,Lazy&lt;Promise&lt;A&gt;&gt;Task&lt;A&gt; 的类型相同。如果要改变这一点,这个解决方案就行不通了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-02
    • 1970-01-01
    • 2018-02-09
    • 2021-09-04
    • 1970-01-01
    • 2020-11-16
    • 1970-01-01
    相关资源
    最近更新 更多