Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List vs Map (key safety vs mapping over all elements)

What is the best data structure for the following scenario?

Say, you have a list of URLs

linkHaskell = Url "http://www.haskell.org"
linkReddit = Url "http://www.reddit.com"
...

and you use them individually, but you also want to operate on all of them, e.g. link-check, you could put them in a list

allLinks = [
    linkHaskell
  , linkReddit
  ...
  ]

But that is error-prone, since you might forget to add a new link.

You could choose to store those URLs in a Map instead, but then you would exchange compile-time errors for runtime-errors, in case you have typos in the keys.

In Haskell what would you do?

like image 534
LennyStackOverflow Avatar asked May 17 '11 09:05

LennyStackOverflow


1 Answers

One simple approach is to define a datatype for the links, i.e.

data Link = LinkHaskell | LinkReddit
    deriving (Enum, Bounded)

toUrl LinkHaskell = Url "http://www.haskell.org"
toUrl LinkReddit  = Url "http://www.reddit.org"

allLinks :: [Link]
allLinks = [minBound .. maxBound]

You still have to specify the name in two places, but at least now the compiler will complain if you forget to add it in one place (at least with -Wall).

Another approach is to use some Template Haskell magic:

{-# LANGUAGE TemplateHaskell #-}

module Links where

import Control.Monad
import Language.Haskell.TH

data Url = Url String
    deriving (Show)

mkLinks :: [(String, String)] -> Q [Dec]
mkLinks links = liftM2 (++) mkAllLinks $ mapM mkLink links
  where
    mkLink (name, url) = valD (varP $ mkLinkName name) (normalB [| Url url |]) []
    mkAllLinks = [d| allLinks = $(listE [varE $ mkLinkName name | (name, _) <- links] )|]
    mkLinkName = mkName . ("link" ++)

Now you only have to specify the links in one place:

{-# LANGUAGE TemplateHaskell #-}

import Links

mkLinks
  [("Haskell", "http://www.haskell.org")
  ,("Reddit",  "http://www.reddit.org")
  ,("StackOverflow", "http://www.stackoverflow.com")
  ]

main = do
    putStrLn "By name:"
    print $ linkHaskell
    print $ linkReddit

    putStrLn "All:"
    mapM_ print allLinks
like image 69
hammar Avatar answered Nov 06 '22 21:11

hammar