1

I am trying to create and use a dictionary with code from here:

import Data.List (lookup)

insert :: Eq a => (a,b) -> [(a,b)] -> [(a,b)]
insert (a,b)  []           = [(a,b)]
insert (a,b) ((c,d):rest) = if a == c
    then (a,b) : rest
    else (c,d) : insert (a,b) rest

dict :: [(String, String)]
dict = [("", "")]

main = do 
    insert ("onekey", "onevalue") dict
    print dict
    print $ lookup "onekey" dict

But I am getting following error:

$ runghc rndict.hs

rndict.hs:22:1: error:
    • Couldn't match expected type ‘IO t0’ with actual type ‘[()]’
    • In the expression: main
      When checking the type of the IO action ‘main’

rndict.hs:24:9: error:
    • Couldn't match type ‘IO’ with ‘[]’
      Expected type: [()]
        Actual type: IO ()
    • In a stmt of a 'do' block: print dict
      In the expression:
        do { insert ("onekey", "onevalue") dict;
             print dict;
             print $ lookup "onekey" dict }
      In an equation for ‘main’:
          main
            = do { insert ("onekey", "onevalue") dict;
                   print dict;
                   print $ lookup "onekey" dict }

rndict.hs:25:9: error:
    • Couldn't match type ‘IO’ with ‘[]’
      Expected type: [()]
        Actual type: IO ()
    • In a stmt of a 'do' block: print $ lookup "onekey" dict
      In the expression:
        do { insert ("onekey", "onevalue") dict;
             print dict;
             print $ lookup "onekey" dict }
      In an equation for ‘main’:
          main
            = do { insert ("onekey", "onevalue") dict;
                   print dict;
                   print $ lookup "onekey" dict }

What is the problem and what is the correct way to use dictionaries in Haskell?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
rnso
  • 23,686
  • 25
  • 112
  • 234
  • see [this](https://stackoverflow.com/a/50815136/849891) for a general description of [tag:do-notation]. – Will Ness May 20 '19 at 12:42

1 Answers1

5

You need to use let to bind the new dictionary to a name:

import Data.List (lookup)

insert :: Eq a => (a,b) -> [(a,b)] -> [(a,b)]
insert (a,b)  []           = [(a,b)]
insert (a,b) ((c,d):rest) = if a == c
    then (a,b) : rest
    else (c,d) : insert (a,b) rest

dict :: [(String, String)]
dict = [("", "")]

main = do
    let d = insert ("onekey", "onevalue") dict
    print d
    print $ lookup "onekey" d

You ask about inserting multiple elements. For this you could use a fold to write a function called insertMany. You should probably be using foldl' but I'll leave that as an exercise to find out why.

import Data.List (lookup)

insert :: Eq a => (a,b) -> [(a,b)] -> [(a,b)]
insert (a,b)  []           = [(a,b)]
insert (a,b) ((c,d):rest) = if a == c
    then (a,b) : rest
    else (c,d) : insert (a,b) rest

insertMany :: Eq a => [(a,b)] -> [(a,b)] -> [(a,b)]
insertMany elements dict =
  foldl (flip insert) dict elements

dict :: [(String, String)]
dict = [("", "")]

main = do
    let d = insert ("onekey", "onevalue") dict
    print d
    print $ lookup "onekey" d
    print $ insertMany [("onekey", "newvalue"), ("anotherkey", "anothervalue")]
      dict
Michiel Borkent
  • 34,228
  • 15
  • 86
  • 149
  • Can I not change original dictionary using `let dict =...` – rnso May 20 '19 at 12:30
  • You would not change it, but introduce a name that shadows the original name. – Michiel Borkent May 20 '19 at 12:30
  • I have to repeat this insert process many times. I see that it is not working on second time itself. – rnso May 20 '19 at 12:31
  • Notice that `insert` does not mutate, but returns a new dictionary every time. If you need to insert many elements, you can make a function `insertMany` that uses a fold or recursion. – Michiel Borkent May 20 '19 at 12:33
  • Ok. I need that as the answer please. – rnso May 20 '19 at 12:34
  • `let`'s bindings are recursive so it wouldn't work with the same name, `let dict = ....dict....`. Instead, *inside a `do` block*, you *can* write `dict <- return (....dict....)`, even if it's a bit hackish. That *will* create a new name shadowing the previous one. – Will Ness May 20 '19 at 12:34
  • Dictionaries / association lists are very useful and there in most languages. What is the correct way to create, add multiple items and to lookup an item to a dictionary in Haskell. That is what I am trying to understand. – rnso May 20 '19 at 12:35
  • see [how to increment a variable in functional programming](https://stackoverflow.com/questions/11922134/how-to-increment-a-variable-in-functional-programming). – Will Ness May 20 '19 at 12:37
  • `d <- (return insert ("2key", "2value") d)` does not work. Gives error: `Couldn't match type ‘[(a0, b0)] -> [(a0, b0)]’ with ‘IO [([Char], b1)]’` – rnso May 20 '19 at 12:39
  • @rnso I added an example of `insertMany`. – Michiel Borkent May 20 '19 at 12:43
  • perhaps `insertMany [("onekey", "newvalue"), ("anotherkey", "anothervalue")]`? – Will Ness May 20 '19 at 12:47
  • @rnso that should've been `d <- return (insert ("2key", "2value") d)`. And yes, you can repeat that as many time as you'd like. Although, it will definitely be bad style; using a fold-based function is much more idiomatic. – Will Ness May 20 '19 at 12:49
  • Thanks for a detailed answer. I would be great if you could also mention if and how I can do `let d = insert (k, v) dict` multiple times to the same `d` with varying values of `k` and `v`. – rnso May 20 '19 at 13:38