Monad:[UI元素] vs [元素]

时间:2016-04-22 03:15:16

标签: haskell threepenny-gui

在下面的代码示例中,我尝试创建一个包含多个select元素的框,并使用行为将它们的选择组合成值列表。 (代码编译/运行在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如何处理所有状态和事件(我还没有使用monad / applicatives),但我怀疑在正确的版本中合并{{1} }} context被添加到&#39; top level&#39;并因此传递给UI(或(#+)?),但在错误版本的UI.mkElement列表中仍未使用。

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

1 个答案:

答案 0 :(得分:2)

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

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

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

在正确的版本中,您使用sequence按顺序执行所有操作,并在列表Element中保留相应的新内容(selectionList')。所有元素都已创建一次,然后您只需将它们传递给下一步。组合器pure构建一个简单地返回现有事物的动作,它起作用。

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