我正在尝试用IO和monads来制作一个简单的示例。程序从DOM中读取一个节点并向其写入一些innerHTML。
我要挂的是IO和可能的组合,例如IO (Maybe NodeList)。
如何短路或在此设置中抛出错误?
我可以使用getOrElse提取一个值或设置一个默认值,但是将默认值设置为一个空数组没有任何帮助。
import R from 'ramda';
import { IO, Maybe } from 'ramda-fantasy';
const Just = Maybe.Just;
const Nothing = Maybe.Nothing;
// $ :: String -> Maybe NodeList
const $ = (selector) => {
const res = document.querySelectorAll(selector);
return res.length ? Just(res) : Nothing();
}
// getOrElse :: Monad m => m a -> a -> m a
var getOrElse = R.curry(function(val, m) {
return m.getOrElse(val);
});
// read :: String -> IO (Maybe NodeList)
const read = selector =>
IO(() => $(selector));
// write :: String -> DOMNode -> IO
const write = text =>
(domNode) =>
IO(() => domNode.innerHTML = text);
const prog = read('#app')
// What goes here? How do I short circuit or error?
.map(R.head)
.chain(write('Hello world'));
prog.runIO();发布于 2017-04-07 03:46:57
您可以尝试编写一个EitherIO单台转换器。monad变压器允许您将两个单体的效果组合成一个单一的单体。它们可以以一种通用的方式编写,这样我们就可以根据需要创建monads的动态组合,但是在这里,我将演示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)我们需要一对辅助函数来将其他的平面类型带到嵌套的monad中
EitherIO.liftEither :: Either e a -> EitherIO e a
EitherIO.liftIO :: IO a -> EitherIO e a为了与幻境相一致,我们的新的EitherIO monad采用了chain方法和of函数,并遵守了monad定律。为了方便您,我还用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'
// ...
// prog :: IO (Either Error String)
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泛型变换
monad转换器将单模作为参数并创建一个新的monad。在本例中,EitherT将使用一些monad M并创建一个具有M (Either e a)的有效行为的monad。
所以现在我们有了一些方法来创造新的单簧管
// 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 aEitherT
是面包和黄油-您在这里看到的主要区别是EitherT接受一个单一M作为输入,并创建/返回一个新的monad类型。
// EitherT.js
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.runEitherTEitherIO
现在可以用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'
// ...
// prog :: () -> Either Error String
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运行的代码
发布于 2017-04-06 20:41:02
如果给定谓词返回true,您可以创建一个助手函数,该函数将有条件地与另一个IO生成函数链接。如果它返回false,它将生成一个IO ()。
// (a → Boolean) → (a → IO ()) → a → IO ()
const ioWhen = curry((pred, ioFn, val) =>
pred(val) ? ioFn(val) : IO(() => void 0))
const $ = document.querySelector.bind(document)
const read = selector =>
IO(() => $(selector))
const write = text => domNode =>
IO(() => domNode.innerHTML = text)
const prog = read('#app').chain(
ioWhen(node => node != null, write('Hello world'))
)
prog.runIO();https://stackoverflow.com/questions/43260076
复制相似问题