Monad: [UI Element] vs [Element]



在下面的代码示例中,我尝试创建一个包含许多选择元素的框,并使用行为将它们的选择组合到值列表中。(代码在GHCI中编译/运行,只需三便士-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/applicative),但我怀疑在正确的版本中,组合的UI上下文被添加到"顶级",从而传递给(#+)(或UI.mkElement?),但在错误版本的UI Element列表中仍未使用。

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

它们的关键点是,类型UI Element表示创建/操作/返回事物的(monadic)操作,而类型Element表示事物本身。有时,前者创造了一个新事物,有时它返回了一个旧事物。

在您的情况下,selectionList :: [UI Element]创建事物的操作列表(每个操作)。

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

在正确的版本中,您可以使用sequence按顺序执行所有操作,并在列表selectionList'中保留相应的新事物(Element)。所有元素都只创建了一次,然后您只需将它们传递给接下来的任何元素即可。组合器pure构建一个动作,该动作仅返回现有事物,这就是它的工作。


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

最新更新