Monad:[UI 元素] 与 [元素]

Monad: [UI Element] vs [Element]

在下面的代码示例中,我尝试创建一个包含多个 select 元素的框,并使用行为将它们的 selection 组合成一个值列表。 (代码 compiles/runs 在 ghci 中只有 threepenny-gui)

{-# LANGUAGE RecursiveDo #-}
module Threepenny.Gui where

import Prelude hiding (lookup)
import Control.Monad
import Data.List
import Data.Traversable
import Data.Maybe
import Data.Monoid
import qualified Data.Map as Map
import qualified Data.Set as Set

import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Core hiding (delete)

{-----------------------------------------------------------------------------
    (#) Reverse function application: flip $
    (#+) Append DOM elements as children to given element: parent #+ children
    (#.) Returns UI Element with CSS class changed to second parameter
------------------------------------------------------------------------------}
gui :: IO ()
gui = startGUI defaultConfig setup

fixedTextarea = UI.textarea # set style [("resize", "none"), ("height", "14px"), ("width", "500px")]

combinedBeh :: MonadIO m => [Element] -> m (Behavior ([Maybe Int]))
combinedBeh sl = sequenceA <$> sequence blist
  where blist  = fmap (stepper Nothing . UI.selectionChange ) sl

selectDivWrong :: UI (Element, Behavior [Maybe Int])
selectDivWrong = do 
  let select options = UI.select 
        # set style [("display","inline-block"), ("width", "150px"), ("margin", "0px 0px 4px 0px")]
        #+ fmap (\x -> UI.option # set UI.text (show x)) options
      selectionList :: [UI Element]
      selectionList = replicate 6 $ select [0, 1, 2, 3, 4, 5]

  selectionList' <- (sequence selectionList :: UI [Element])
  bSelectionList <- combinedBeh selectionList'
  mainBox        <- UI.mkElement "selectDiv"
    # set style [("display","inline-block"), ("background-color", "#333344"),
     ("height", "200px"), ("width", "150px"), ("padding", "1px")] --
    #+ (selectionList) -- unsequenced list of UI elements. The behavior (bSelectionList) should have all the info it needs though(?).
-- why does (#+) not have the same UI info as bSelectionList ?

  return (mainBox, bSelectionList)

selectDivCorrect :: UI (Element, Behavior [Maybe Int])
selectDivCorrect = do 
  let select options = UI.select 
        # set style [("display","inline-block"), ("width", "150px"), ("margin", "0px 0px 4px 0px")]
        #+ fmap (\x -> UI.option # set UI.text (show x)) options
      selectionList :: [UI Element]
      selectionList = replicate 6 $ select [0, 1, 2, 3, 4, 5]

  selectionList' <- (sequence selectionList :: UI [Element])
  bSelectionList <- combinedBeh selectionList'
  mainBox        <- UI.mkElement "selectDiv"
    # set style [("display","inline-block"), ("background-color", "#333344"),
     ("height", "200px"), ("width", "150px"), ("padding", "1px")] --
    #+ (fmap pure selectionList')

  return (mainBox, bSelectionList)

setup :: Window -> UI ()
setup window = void $ mdo
  (sDiv1, bSDiv) <- selectDivWrong
  text1   <- fixedTextarea # sink UI.text (show <$> bSDiv) 

  (sDiv2, bSDiv2) <- selectDivCorrect
  text2   <- fixedTextarea # sink UI.text (show <$> bSDiv2) 


  getBody window 
    #+ [grid
        [ [element sDiv1]
        , [element text1]
        , [element sDiv2]
        , [element text2]
        ]]  
    # set style [("background-color", "#eeeeee")]

最初我想使用selectDivWrong,但发现我需要将其修改为selectDivCorrect。我的问题是我不明白为什么会有功能上的差异。在这两种情况下,selectionList 包含所有需要添加的元素,而 bSelectionList 组合所有行为。我不确定 UI 如何处理所有状态和事件(而且我还没有使用 monads/applicatives 很多),但我怀疑在正确的版本中 UI上下文被添加到 'top level' 并因此传递给 (#+) (或 UI.mkElement ?),但在错误版本的 UI Element 列表中仍未使用。

不过,我仍然不确定我是否遗漏了什么。我真的很想确定并找到有助于将来识别此类问题的解释,因为我基本上是通过反复试验找到了解决方案。 (也可以随意重命名问题...)

他们的关键点是类型 UI Element 表示 creates/manipulates/returns 事物的(单子)动作,而类型 Element 表示事物本身。有时,前者创造了一个新事物,有时它 returns 一个旧事物。

在您的例子中,selectionList :: [UI Element] 创建事物 (每个)的操作列表。

在错误的版本中,您首先使用 sequence 依次执行所有操作,但随后您还将列表传递给 #+ 组合器,后者在内部执行所有操作顺序也是如此。因此,动作被执行了两次,所以每个元素被创建了两次。

在正确的版本中,你使用sequence依次执行所有动作,并在列表selectionList'中保留相应的新事物(Element)。所有元素都已创建一次,然后您只需将它们传递给下一个元素。组合器 pure 构建一个简单的操作 returns 一个现有的东西,就是这样。


最后,#+的类型可能有点混乱,如果它接受列表[Element]而不是列表[UI Element]会更透明。不过,后者减少了语法噪音,因为它减少了命名单个元素的需要。