如何进入内部 Maybe monad 从纯脚本中的 html 按钮中提取 class 名称?

How to get to inner Maybe monad to extract class name from html button in purescript?

我正在尝试学习 purescript

我在某些 HTML 中有一个按钮,我正在尝试打印其 class 名称。我正在构建 browserifying 使用 pulp

我使用的函数是querySelector:

import Web.DOM.ParentNode (querySelector)

这个returns我想要的项目,Element,在两个"boxes"内:一个外部Effect monad和一个嵌入式Maybe monad:

> :type querySelector
QuerySelector -> ParentNode -> Effect (Maybe Element)

我的 Effect monad 看起来像:

getBtn ::  Effect Unit
getBtn = do
  doc <- map toParentNode (window >>= document)
  button <- querySelector (wrap "#btn") doc
  ... Need to extract class name from 'button' here

我知道我可以通过在外部 Effect monad 上调用 bind (>>=) 来访问内部 Maybe。我的第一个攻击计划是使用 maybe 函数手动拆箱 Maybe。以下是对简单 Int 执行此操作的方法:

> maybe 0 (\x -> x) $  Just 7
7

对于 可能的元素 我认为它看起来像:

unboxMaybeElement :: Maybe Element -> Element
unboxMaybeElement Nothing = Nothing
unboxMaybeElement (Just a) = maybe (Element ..note: trying to create a default element here) (\x -> x) a 

一个问题是我找不到类型 Element 的构造函数,所以我无法提供默认值( 的第一个参数可能), 所以我不能使用这个方法。

此外,我找不到 Element 类型的定义。

我还读到,无论如何,进入 Maybe 内部并不是一个好主意,而是通过使用仿函数(如 map 或 fmap)来提升反对它的功能。

在这种情况下,我尝试调用一个外部函数,例如:

  button <- querySelector (wrap "#btn") doc
  let cn = getClassName button
  log $ "classname=" <> show cn

-- The basic question is how to implement this function?
getClassName :: Maybe HTMLElement -> String
getClassName e = map className  (map fromElement e)

注意:classNamefromElement 在 Web.HTML.HTMLElement.

中提供

如您所见,我正在尝试通过使用映射调用函数来提升它们,因为这些函数处理 Maybes,但我正在陷入 "Maybe Element" 和 "Element" 以及 "HTMLElement" 与 "Element" 等类型冲突的泥潭

是的,我承认我在这里遇到了一些问题,这并不是一个真正的初学者问题,但让我感到恼火的是获得 class 名称这样简单的事情HTML 物体太硬了。当然,我一定错过了什么?

顺便说一句,这是我的 html:

<!doctype html>
<html>
  <body>
    <script src="index.js"></script>
    <p>
      <input type="button" class="btn-class" value="click me" id="btn"/>
    </p>
  </body>
</html>

更普遍的问题是:如何获得嵌套两个 monad 层深的值?

基于我在 this video 中看到的一些类似代码,我能够得到编译的东西。我并不是说这是最好的方法,甚至不是一个好方法,只是它有效:

getBtn ::  Effect Unit
getBtn = do
  log "now in getBtn"
  doc <- map toParentNode (window >>= document)
  mbtn <- querySelector (wrap "#btn") doc
  case mbtn of
    Nothing -> log "mbtn failed"
    Just btn -> do
      let mhtmlEl = fromElement btn
      case mhtmlEl of
        Nothing -> log "mhtml failed"
        Just htmlEl -> do
          let cn = className htmlEl
          log $ "classname below:"
          cn >>= log
          pure unit
      pure unit
  pure unit
pure unit

某些变量名("mbtn"、"mhtmlEl")上的 "m" 大概是表示它是一个包装对象,即单子。重要的是,在 case Just 语句中指定的变量不同于 case of 语句中的变量,前面没有 "m" 的变量("btn"、"htmlEl"),表示它是原始值或未包装值。

pure unit 行本质上是虚拟行,以满足 monad 总是 returns 除了 "binder"(let 或 '<-' 赋值)之外的东西的要求,所以您可能需要也可能不需要它们,具体取决于您的 do 工作流程的构建方式。

在这个例子中实际上有两个级别的 Maybe:一个在 querySelector 结果上,一个在 fromElement 结果上。添加另一个 do 来处理原始 HTMLElement,您最多可以达到三个级别的 do

不幸的是,querySelector 似乎为我返回 Nothing,但这是一个正交问题。这就是打印 className 的全部动机——确定它是否找到了元素。

实际上你自己调查这个问题做得很好,我可以根据你提供的例子建议重构。

假设我们不关心在特定情况下记录失败消息。这意味着,我们可以用 pure unit:

替换每个这样的日志记录
getBtn ::  Effect Unit
getBtn = do
  log "now in getBtn"
  doc <- map toParentNode (window >>= document)
  mbtn <- querySelector (wrap "#btn") doc
  case mbtn of
    Nothing -> pure unit
    Just btn -> do
      let mhtmlEl = fromElement btn
      case mhtmlEl of
        Nothing -> pure unit
        Just htmlEl -> do
          let cn = className htmlEl
          log $ "classname below:"
          cn >>= log

然后我们可以在这里注意到一个有趣的模式:

case mbtn of
  Nothing -> pure unit
  Just btn -> do
    let mhtmlEl = fromElement btn

即我们想在 btnJust btn 上应用 fromElement 函数,如果它是 Nothing 则什么都不做。结果类型必须是 Maybe 值。

首先想到的是使用map函数,但是fromElement的类型是fromElement :: Element -> Maybe HTMLElement,所以结果类型会是Maybe (Maybe a)).

无论如何,我们甚至可以search such a function by type and the first result is bind(与(>>=)相同)。因此,让我们重构(为了清楚起见,将类型指向注释,但确保没有必要):

getBtn ::  Effect Unit
getBtn = do
  log "now in getBtn"
  doc <- map toParentNode (window >>= document)
  mbtn <- querySelector (wrap "#btn") doc

  let mhtmlEl = fromElement =<< mbtn -- Maybe HTMLElement

  case mhtmlEl of
    Nothing -> pure unit
    Just htmlEl -> do
      let cn = className htmlEl
      log $ "classname below:"
      cn >>= log

下一步是减少另一个 case 表达式。这里使用 map(与 <$> 相同)就足够了:

getBtn ::  Effect Unit
getBtn = do
  log "now in getBtn"
  doc <- map toParentNode (window >>= document)
  mbtn <- querySelector (wrap "#btn") doc

  let mhtmlEl = (fromElement =<< mbtn) -- Maybe HTMLElement
  let mCn = className <$> mhtmlEl -- Maybe (Effect String)

  case mCn of
      Nothing -> log "no such element"
      Just cn -> do
          log $ "classname below:"
          cn >>= log

getBtn 函数的结果类型必须是 Effect Unit,因此必须在此处处理每个 Maybe 值的情况。我会这样保留它,因为它很清楚,这里发生了什么。但是如果你追求简洁的表示,那么可以在这里应用maybe函数:

getBtn ::  Effect Unit
getBtn = do
  log "now in getBtn"
  doc <- map toParentNode (window >>= document)
  mbtn <- querySelector (wrap "#btn") doc

  let mhtmlEl = (fromElement =<< mbtn) -- Maybe HTMLElement
  let mCn = className <$> mhtmlEl -- Maybe (Effect String)

  maybe (log "no such element") (\cn -> log "classname below:" *> cn >>= log) mCn