为 DOM read/write 组合 Maybe 和 IO monad
Combining Maybe and IO monads for DOM read/write
我正在尝试编写一个简单的示例使用 IO 和 Maybe monads。该程序从 DOM 中读取一个节点并向其写入一些 innerHTML
。
我挂断的是 IO 和 Maybe 的组合,例如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();
您可以创建一个辅助函数,如果给定的谓词 returns 为真,它将有条件地与另一个 IO 生成函数链接。如果它 returns 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();
您可以尝试编写一个 EitherIO
monad 转换器。 Monad 转换器允许您将两个 monad 的效果组合成一个 monad。它们可以用通用的方式编写,这样我们就可以根据需要创建 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)
我们需要一些辅助函数来将其他平面类型引入我们的嵌套 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 作为参数并创建一个新的 monad。在这种情况下,EitherT
将采用一些 monad M
并创建一个有效行为的 monad 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 a
任一个
是面包和黄油 - 你在这里看到的主要区别是 EitherT
接受一个 monad M
作为输入和 creates/returns 一个新的 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.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'
// ...
// 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
我正在尝试编写一个简单的示例使用 IO 和 Maybe monads。该程序从 DOM 中读取一个节点并向其写入一些 innerHTML
。
我挂断的是 IO 和 Maybe 的组合,例如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();
您可以创建一个辅助函数,如果给定的谓词 returns 为真,它将有条件地与另一个 IO 生成函数链接。如果它 returns 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();
您可以尝试编写一个 EitherIO
monad 转换器。 Monad 转换器允许您将两个 monad 的效果组合成一个 monad。它们可以用通用的方式编写,这样我们就可以根据需要创建 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)
我们需要一些辅助函数来将其他平面类型引入我们的嵌套 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 作为参数并创建一个新的 monad。在这种情况下,EitherT
将采用一些 monad M
并创建一个有效行为的 monad 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 a
任一个
是面包和黄油 - 你在这里看到的主要区别是 EitherT
接受一个 monad M
作为输入和 creates/returns 一个新的 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.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'
// ...
// 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