haskellhaskell-stackghcjsreflexreflex-dom

Cannot map through Dynamic: No instance for (Functor (Dynamic Spider))


My intention is to alternate the colour of a div between bright red and dark red as a button gets pressed, starting with dark red.

I have this code:

{-# LANGUAGE
    OverloadedStrings
  #-}

module Main where

import Data.Map (Map)
import Reflex.Dom

main = mainWidget $ do
    x <- greenButton
    y <- toggle False x
    let z = fmap style y
    elDynAttr "div" z blank

style :: Bool -> Map String String
style b | b         = "style" =: "height: 10ex; width: 10ex; background-color: #f00;"
        | otherwise = "style" =: "height: 10ex; width: 10ex; background-color: #900;"

greenButton :: MonadWidget t m => m (Event t ())
greenButton = button "[ ]"  -- Should be green but whatever.

It errors out thus:

• No instance for (Functor (Dynamic Spider))
    arising from a use of ‘fmap’
• In the expression: fmap style y
  In an equation for ‘z’: z = fmap style y
  In the second argument of ‘($)’, namely
    ‘do { x <- greenButton;
          y <- toggle False x;
          let z = fmap style y;
          elDynAttr "div" z blank }’

I certainly see an fmap for Dynamic in the quick reference, though I am not sure the version of the reference and the version of the reflex package I compile against are consistent.

This is the stack.yaml I use for building:

resolver: lts-7.19
compiler: ghcjs-0.2.1.9007019_ghc-8.0.1
compiler-check: match-exact

setup-info:
  ghcjs:
    source:
      ghcjs-0.2.1.9007019_ghc-8.0.1:
           url: http://ghcjs.tolysz.org/ghc-8.0-2017-02-05-lts-7.19-9007019.tar.gz
           sha1: d2cfc25f9cda32a25a87d9af68891b2186ee52f9

extra-deps:
- reflex-dom-0.3
- ghcjs-dom-0.2.4.0
- ref-tf-0.4.0.1
- reflex-0.4.0.1

allow-newer: true

What am I doing wrong? And who the hell is this guy Spider?


Solution

  • The discussion below explains why Functor for Dynamic turned out to be a bad idea.

    https://github.com/reflex-frp/reflex/pull/39

    The problem with this instance is that it will evaluate f twice whenever the input dynamic changes: once to compute the new Event value, and once to compute the new Behavior value. With mapDyn, there is only a single computation, the result of both is shared. This is also why mapDyn is monadic.


    So the code would look like this:

    main = mainWidget $ do
        x <- greenButton
        y <- toggle False x
        z <- mapDyn style y      -- monadic mapDyn instead of fmap
        elDynAttr "div" z blank
    

    On the issue of documentation, Quickref.md for version 0.4.0.1 is here and only references mapDyn.

    The Quickref.md you linked to is the one for the future reflex-0.5 (the current develop branch), which is reworking Dynamic to have a Functor instance. See their wiki:

    Why doesn't Dynamic have Functor/Applicative/Monad instances

    Reflex is scheduled to get these instances in version 0.5. As of this writing the implementation is mostly complete and is undergoing final polish and testing before release and is currently available on github in reflex's develop branch.