首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >组合用于DOM读/写的可能和IO单体

组合用于DOM读/写的可能和IO单体
EN

Stack Overflow用户
提问于 2017-04-06 15:52:29
回答 2查看 1K关注 0票数 5

我正在尝试用IO和monads来制作一个简单的示例。程序从DOM中读取一个节点并向其写入一些innerHTML

我要挂的是IO和可能的组合,例如IO (Maybe NodeList)

如何短路或在此设置中抛出错误?

我可以使用getOrElse提取一个值或设置一个默认值,但是将默认值设置为一个空数组没有任何帮助。

代码语言:javascript
复制
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();

https://www.webpackbin.com/bins/-Kh2ghQd99-ljiPys8Bd

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-04-07 03:46:57

您可以尝试编写一个EitherIO单台转换器。monad变压器允许您将两个单体的效果组合成一个单一的单体。它们可以以一种通用的方式编写,这样我们就可以根据需要创建monads的动态组合,但是在这里,我将演示EitherIO的静态耦合。

首先我们需要一条从IO (Either e a)EitherIO e a的路,还有一条从EitherIO e aIO (Either e a)的路

代码语言:javascript
复制
EitherIO :: IO (Either e a) -> EitherIO e a
runEitherIO :: EitherIO e a -> IO (Either e a)

我们需要一对辅助函数来将其他的平面类型带到嵌套的monad中

代码语言:javascript
复制
EitherIO.liftEither :: Either e a -> EitherIO e a
EitherIO.liftIO :: IO a -> EitherIO e a

为了与幻境相一致,我们的新的EitherIO monad采用了chain方法和of函数,并遵守了monad定律。为了方便您,我还用map方法实现了函子接口。

EitherIO.js

代码语言:javascript
复制
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

更好的是,您的readwrite函数都很好--程序中除了在prog中构造调用之外,什么都不需要改变

代码语言:javascript
复制
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())

附加解释

代码语言:javascript
复制
// 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'。重新运行该程序,您将看到控制台中出现了相应的错误。

代码语言:javascript
复制
Error: Could not find DOMNode

可运行演示

你走了这么远。下面是一个可运行的演示,作为您的奖励:https://www.webpackbin.com/bins/-Kh5NqerKrROGRiRkkoA

使用的EitherT泛型变换

monad转换器将单模作为参数并创建一个新的monad。在本例中,EitherT将使用一些monad M并创建一个具有M (Either e a)的有效行为的monad。

所以现在我们有了一些方法来创造新的单簧管

代码语言:javascript
复制
// EitherIO :: IO (Either e a) -> EitherIO e a
const EitherIO = EitherT (IO)

此外,我们还具有将平面类型提升为嵌套类型的函数。

代码语言:javascript
复制
EitherIO.liftEither :: Either e a -> EitherIO e a
EitherIO.liftIO :: IO a -> EitherIO e a

最后,一个使处理嵌套IO (Either e a)类型更容易的自定义运行函数--注意,一个抽象层(IO)被移除,所以我们只需要考虑Either

代码语言:javascript
复制
runEitherIO :: EitherIO e a -> Either e a

EitherT

是面包和黄油-您在这里看到的主要区别是EitherT接受一个单一M作为输入,并创建/返回一个新的monad类型。

代码语言:javascript
复制
// 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实现--这是一个非常简化的实现。

代码语言:javascript
复制
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()

更新到我们的程序

代码语言:javascript
复制
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运行的代码

票数 3
EN

Stack Overflow用户

发布于 2017-04-06 20:41:02

如果给定谓词返回true,您可以创建一个助手函数,该函数将有条件地与另一个IO生成函数链接。如果它返回false,它将生成一个IO ()

代码语言:javascript
复制
// (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();
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43260076

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档