title: Haskell: Reading from FS to "dynamically" create widgets in taffybar
body: <p>I'm trying to use taffybar as my status bar (uses Dyre framework so configuration is code). It has a widget that can show network interface statistics. In the default configuration this widget requires a <code>String</code> at compile time. I want it to create a widget per (non-loopback) interface dynamically instead.</p>
  
  <p>This is what I have so far:</p>
  
  <pre><code>listNetworkDevices :: IO [String]
  listNetworkDevices = fmap (map takeBaseName) $ getDirectoryContents "/sys/class/net/"
  
  filterOutLoopback :: [String] -&gt; [String]
  filterOutLoopback = filter (\y -&gt; not (elem y ["", ".", "lo"]))
  
  netDevList :: IO [String]
  netDevList = fmap filterOutLoopback listNetworkDevices
  
  ...
  
  let nets = fmap (fmap (netMonitorNew 1)) netDevList
      ...
  defaultTaffybar defaultTaffybarConfig {
      ...
      endWidgets = [ tray, clock, mem, cpu] ++ nets ++ [ bat ]
  }
  </code></pre>
  
  <p>At this point I'm down to the following compiler error:</p>
  
  <pre><code>Couldn't match expected type ‘[IO gtk-0.14.2:Graphics.UI.Gtk.Types.Widget]’
              with actual type ‘IO [IO gtk-0.14.2:Graphics.UI.Gtk.Types.Widget]’
  </code></pre>
  
  <p>Overall this the error makes sense since I'm making an <code>IO Widget</code> out of every <code>String</code>. So <code>IO [String]</code> turns into <code>IO [IO Widget]</code>.</p>
  
  <p>What I don't understand is how to avoid doing this. Clearly I must have gone wrong at some point but I can't see where. I'm not even sure how to put the issue in words. I would be glad for any pointers!</p>
  
  <p>Relevant material:</p>
  
  <ul>
  <li>endWidgets docs: <a href="https://hackage.haskell.org/package/taffybar-0.4.6/docs/System-Taffybar.html#v:endWidgets" rel="nofollow">https://hackage.haskell.org/package/taffybar-0.4.6/docs/System-Taffybar.html#v:endWidgets</a></li>
  <li>netMonitorNew docs: <a href="https://hackage.haskell.org/package/taffybar-0.4.6/docs/System-Taffybar-NetMonitor.html#v:netMonitorNew" rel="nofollow">https://hackage.haskell.org/package/taffybar-0.4.6/docs/System-Taffybar-NetMonitor.html#v:netMonitorNew</a></li>
  <li>Source on github: <a href="https://github.com/travitch/taffybar/tree/master/src/System/Taffybar" rel="nofollow">https://github.com/travitch/taffybar/tree/master/src/System/Taffybar</a></li>
  </ul>
  
  <p>Edit: complete listing:</p>
  
  <pre><code>import System.Taffybar
  import System.Taffybar.Systray
  import System.Taffybar.Pager
  import System.Taffybar.TaffyPager
  import System.Taffybar.SimpleClock
  import System.Taffybar.Battery
  import System.Taffybar.NetMonitor
  import System.Taffybar.Weather
  import System.Taffybar.Widgets.PollingBar
  import System.Taffybar.Widgets.PollingGraph
  import System.Information.Memory
  import System.Information.CPU
  import System.Directory ( getDirectoryContents )
  import System.FilePath ( takeBaseName )
  import Control.Monad
  
  memCallback = do
    mi &lt;- parseMeminfo
    return [memoryUsedRatio mi]
  
  cpuCallback = do
    (userLoad, systemLoad, totalLoad) &lt;- cpuLoad
    return [totalLoad, systemLoad]
  
  listNetworkDevices :: IO [String]
  listNetworkDevices = fmap (map takeBaseName) $ getDirectoryContents "/sys/class/net/"
  
  filterOutLoopback :: [String] -&gt; [String]
  filterOutLoopback = filter (\y -&gt; not (elem y ["", ".", "lo"]))
  
  netDevList :: IO [String]
  netDevList = fmap filterOutLoopback listNetworkDevices
  
  myPagerConfig = defaultPagerConfig {}
  
  main = do
    let memCfg = defaultGraphConfig { graphDataColors = [(1, 0, 0, 1)] }
        cpuCfg = defaultGraphConfig { graphDataColors = [ (0, 1, 0, 1)
                                                        , (1, 0, 1, 0.5)
                                                        ]
                                    }
        clock = textClockNew Nothing "&lt;span fgcolor='orange'&gt;%a %b %d %H:%M&lt;/span&gt;" 1
        pager = taffyPagerNew myPagerConfig
        mem = pollingGraphNew memCfg 1 memCallback
        cpu = pollingGraphNew cpuCfg 1 cpuCallback
        bat = textBatteryNew "$percentage$% $time$" 1
        tray = systrayNew
  
        --nets = [ netMonitorNew 1 "wlp0s2" ]
        nets = fmap  (fmap (netMonitorNew 1)) netDevList
  
    defaultTaffybar defaultTaffybarConfig { startWidgets = [ pager ]
                                          , endWidgets = [ tray, clock, mem, cpu] ++ nets ++ [ bat ]
                                          }
  </code></pre>
  
  <p>Edit: Error message:</p>
  
  <pre><code>.config/taffybar/taffybar.hs:
      Couldn't match expected type ‘[IO
                                       gtk-0.14.2:Graphics.UI.Gtk.Types.Widget]’
                  with actual type ‘IO [IO gtk-0.14.2:Graphics.UI.Gtk.Types.Widget]’
      In the first argument of ‘(++)’, namely ‘nets’
      In the second argument of ‘(++)’, namely ‘nets ++ [bat]’
  </code></pre>