Javascript 结合Maybe和IO monad进行DOM读/写

Javascript 结合Maybe和IO monad进行DOM读/写,javascript,functional-programming,monads,ramda.js,fantasyland,Javascript,Functional Programming,Monads,Ramda.js,Fantasyland,我试图用IO和monads编写一个简单的例子。程序从DOM中读取一个节点,并向其中写入一些innerHTML 我最担心的是IO和Maybe的组合,例如IO Maybe NodeList 如何在此设置中短路或抛出错误 我可以使用getOrElse提取一个值或设置一个默认值,但是将默认值设置为一个空数组并没有任何帮助 import R from 'ramda'; import { IO, Maybe } from 'ramda-fantasy'; const Just = Maybe.Just

我试图用IO和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();

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

您可以尝试编写一个EitherIO monad转换器。Monad transformers允许您将两个Monad的效果组合成一个Monad。它们可以用一种通用的方式编写,这样我们就可以根据需要创建单子的动态组合,但在这里,我只想演示一下两者之间的静态耦合

首先,我们需要一种从IO e a到IO e a的方法,以及一种从IO e a到IO e a的方法

我们需要一对助手函数,用于将其他平面类型带到嵌套的monad

EitherIO.liftEither :: Either e a -> EitherIO e a
EitherIO.liftIO :: IO a -> EitherIO e a
为了符合幻想之地,我们的新EitherIO monad采用了链式方法和功能,并遵循monad法则。为了方便起见,我还使用map方法实现了functor接口

EitherIO.js

调整您的程序以使用EitherIO

这一点的好处在于,您的读写函数都很好——除了我们如何在prog中构造调用之外,程序中没有任何东西需要更改

补充说明

检查错误

继续并将“app”更改为一些不匹配的选择器,例如“foo”。重新运行程序,您将看到相应的错误被输入控制台

Error: Could not find DOMNode
可运行演示

你走了这么远。以下是一个可运行的演示,作为您的奖励:

基于EitherT的泛型变换

monad转换器将monad作为参数并创建一个新的monad。在这种情况下,EitherT将获取一些monad M并创建一个有效地表现为has M或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变得更容易。注意,一层抽象IO被删除了,所以我们只需要考虑其中一层

艾瑟特

是面包和黄油-您在这里看到的主要区别是,EitherT接受monad 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.runEitherT
艾瑟里奥

现在可以在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()
更新我们的计划

使用EitherT运行演示


下面是使用EitherT的可运行代码:

这里有一个版本,使用了其中一个而不是Maybe:Maybe这是正确的方法…感谢您的详细解释!我会再读一遍,直到我明白为止。不客气。《蒙纳德变形金刚》对我来说是一项新的研究,花了整整一周的阅读时间才开始对它们有了很好的理解。我想我本来是把它们弄得太复杂了!我想介绍编写一个通用的转换器EitherT,它可以让您执行类似const EitherIO=eitherti的操作。这让我们创建单子的动态嵌套,而不是我在回答中所写的静态耦合。如果我有时间的话,我会在这个周末试着解决这个问题。我在自己的研究中发现了这个问题。我不确定它是否有用。akh的存储库非常有用、彻底,但设计得相当精良。你将不得不四处寻找几个文件,看看它们是如何结合在一起的,但都写得很好。我修改了akh的Either transformer中的一些代码,并在这里为你实现了一个简化版本,更新了这个答案。Monad变形金刚非常有趣!
Error: Could not find DOMNode
// 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
runEitherIO :: EitherIO e a -> Either e a
// 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
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())