您可以尝试编写一个
EitherIO
单子变换器。Monad变换器允许将两个单子的效果组合成一个单一的单子。它们可以以通用的方式编写,使我们可以根据需要创建动态组合的单子,但这里我只是演示了
Either
和
IO
的静态耦合。
首先,我们需要一种方法将
IO(Either e a)
转化为
EitherIO e a
,以及一种方法将
EitherIO e a
转化为
IO(Either e a)
。
EitherIO :: IO (Either e a) -> EitherIO e a
runEitherIO :: EitherIO e a -> IO (Either e a)
我们需要一些辅助函数来将其他平面类型转换为我们的嵌套单子。
EitherIO.liftEither :: Either e a -> EitherIO e a
EitherIO.liftIO :: IO a -> EitherIO e a
为了符合fantasy land规范,我们的新的
EitherIO
单子有一个
chain
方法和
of
函数,并遵守单子法则。为了方便起见,我还使用
map
方法实现了函子接口。
EitherIO.js
import { IO, Either } from 'ramda-fantasy'
const { Left, Right, either } = Either
// type EitherIO e a = IO (Either e a)
export const EitherIO = runEitherIO => ({
// runEitherIO :: IO (Either e a)
runEitherIO,
// map :: EitherIO e a => (a -> b) -> EitherIO e b
map: f =>
EitherIO(runEitherIO.map(m => m.map(f))),
// chain :: EitherIO e a => (a -> EitherIO e b) -> EitherIO e b
chain: f =>
EitherIO(runEitherIO.chain(
either (x => IO.of(Left(x)), (x => f(x).runEitherIO))))
})
// of :: a -> EitherIO e a
EitherIO.of = x => EitherIO(IO.of(Right.of(x)))
// liftEither :: Either e a -> EitherIO e a
export const liftEither = m => EitherIO(IO.of(m))
// liftIO :: IO a -> EitherIO e a
export const liftIO = m => EitherIO(m.map(Right))
// runEitherIO :: EitherIO e a -> IO (Either e a)
export const runEitherIO = m => m.runEitherIO
将程序改为使用EitherIO
这个方法的好处是,您的read
和write
函数可以保持原样 - 您的程序中除了如何在prog
中调用它们外,其他什么都不需要改变。
import { compose } from 'ramda'
import { IO, Either } from 'ramda-fantasy'
const { Left, Right, either } = Either
import { EitherIO, liftEither, liftIO } from './EitherIO'
const prog =
EitherIO(read('#app'))
.chain(compose(liftIO, write('Hello world')))
.runEitherIO
either (throwError, console.log) (prog.runIO())
附加说明
// prog :: IO (Either Error String)
const prog =
// read already returns IO (Either String DomNode)
// so we can plug it directly into EitherIO to work with our new type
EitherIO(read('#app'))
// write only returns IO (), so we have to use liftIO to return the correct EitherIO type that .chain is expecting
.chain(compose(liftIO, write('Hello world')))
// we don't care that EitherIO was used to do the hard work
// unwrap the EitherIO and just return (IO Either)
.runEitherIO
// this actually runs the program and clearly shows the fork
// if prog.runIO() causes an error, it will throw
// otherwise it will output any IO to the console
either (throwError, console.log) (prog.runIO())
检查错误
请将'#app'
更改为不匹配的选择器(例如)'#foo'
。重新运行程序,您将看到适当的错误信息在控制台中显示。
Error: Could not find DOMNode
可运行的演示
您已经走到了这一步。这里有一个可运行的演示作为您的奖励:https://www.webpackbin.com/bins/-Kh5NqerKrROGRiRkkoA
使用EitherT的通用转换
单子变换器以单子作为参数并创建新的单子。在这种情况下,EitherT
将采取一些单子M
并创建一个实际上表现为M (Either e a)
的单子。
所以现在我们有了一些创建新单子的方法。
// EitherIO :: IO (Either e a) -> EitherIO e a
const EitherIO = EitherT (IO)
我们再次有了将平面类型提升到嵌套类型的函数
EitherIO.liftEither :: Either e a -> EitherIO e a
EitherIO.liftIO :: IO a -> EitherIO e a
最后,一个自定义的运行函数使得处理嵌套的
IO (Either e a)
类型更加容易 - 注意,一层抽象 (
IO
) 被移除,因此我们只需要考虑
Either
。
runEitherIO :: EitherIO e a -> Either e a
EitherT
EitherT
是IT技术中的重要概念,其主要作用是接受一个单子(Monad)M
作为输入,并创建/返回一个新的单子类型。
它是IT技术中的“面包和黄油”,是必不可少的。
import { Either } from 'ramda-fantasy'
const { Left, Right, either } = Either
export const EitherT = M => {
const Monad = runEitherT => ({
runEitherT,
chain: f =>
Monad(runEitherT.chain(either (x => M.of(Left(x)),
x => f(x).runEitherT)))
})
Monad.of = x => Monad(M.of(Right(x)))
return Monad
}
export const runEitherT = m => m.runEitherT
EitherIO
现在可以使用EitherT
实现 - 这是一个极其简化的实现。
import { IO, Either } from 'ramda-fantasy'
import { EitherT, runEitherT } from './EitherT'
export const EitherIO = EitherT (IO)
// liftEither :: Either e a -> EitherIO e a
export const liftEither = m => EitherIO(IO.of(m))
// liftIO :: IO a -> EitherIO e a
export const liftIO = m => EitherIO(m.map(Either.Right))
// runEitherIO :: EitherIO e a -> Either e a
export const runEitherIO = m => runEitherT(m).runIO()
我们的程序更新内容
import { EitherIO, liftEither, liftIO, runEitherIO } from './EitherIO'
const prog = () =>
runEitherIO(EitherIO(read('#app'))
.chain(R.compose(liftIO, write('Hello world'))))
either (throwError, console.log) (prog())
使用EitherT的可运行演示
以下是使用EitherT的可运行代码:https://www.webpackbin.com/bins/-Kh8S2NZ8ufBStUSK1EU