Madalina
Madalina

Reputation: 85

Change a String in Haskell

I have this function that must check if a character from a string is a letter, a digit or a punctuation sign, case when it returns True otherwise False. But if I test it, it doesn't work.

punct :: Char -> Bool
punct c | c `elem` ['.', ',', '?', '!', ':', ';', '(', ')'] = True 
        | c isDigit = True  
        | c isAlpha = True 
        | otherwise = False

This function adds a space after one of those above.

format :: String -> String
format s = intercalate "" [if (punct c) then (c:" ") else [c] | c <- s]

After That I need to delete those above, checked at punct. Actually I think that I can replace those from punct directly with space I've tried something like this:

format :: String -> String
format s = map (\c -> if punct c then (c:" ") else [c] )

But it's not working, any suggestions? I think i can use map and filter? Am I wrong?

Upvotes: 2

Views: 236

Answers (2)

Daniel Martin
Daniel Martin

Reputation: 23548

Well, this code won't compile. Let's tackle why it won't compile one issue at a time:

First off, in your punct function, this compilation error happens twice:

Foo.hs:7:11: error:
    • Couldn't match expected type ‘(Char -> Bool) -> Bool’
                  with actual type ‘Char’
    • The function ‘c’ is applied to one argument,
      but its type ‘Char’ has none
      In the expression: c isDigit
      In a stmt of a pattern guard for
                     an equation for ‘punct’:
        c isDigit

What the compiler is trying to tell you here is that you wrote "call c as a function, passing it the argument isDigit", when obviously you meant that the other way around. So first we swap those:

punct :: Char -> Bool
punct c | c `elem` ['.', ',', '?', '!', ':', ';', '(', ')'] = True 
        | isDigit c = True  
        | isAlpha c = True 
        | otherwise = False

With that fixed, your first format function in fact compiles:

format1 :: String -> String
format1 s = intercalate "" [if (punct c) then (c:" ") else [c] | c <- s]

And it also seems to work as advertised:

*Foo> format1 "a.b_c"
"a . b _c "

Okay, now what about that second format function? Well, at first it won't compile:

Foo.hs:18:12: error:
    • Couldn't match type ‘[Char] -> [[Char]]’ with ‘[Char]’
      Expected type: String
        Actual type: [Char] -> [[Char]]
    • Probable cause: ‘map’ is applied to too few arguments
      In the expression: map (\ c -> if punct c then (c : " ") else [c])
      In an equation for ‘format’:
          format s = map (\ c -> if punct c then (c : " ") else [c])

The compiler is telling you here (with the "Probable cause" message) that you forgot to use s on the right hand side, and should have written:

format s = map (\c -> if punct c then (c:" ") else [c] ) s

But event that won't compile: (but the error message is different now)

Foo.hs:18:12: error:
    • Couldn't match type ‘[Char]’ with ‘Char’
      Expected type: String
        Actual type: [[Char]]
    • In the expression:
        map (\ c -> if punct c then (c : " ") else [c]) s
      In an equation for ‘format’:
          format s = map (\ c -> if punct c then (c : " ") else [c]) s

That's saying that your result should have been a String (that is, a list of Char, or [Char]), but instead was a list of Strings (that is, a [[Char]])

This is because you turned each character into a string, instead of into a character. One way to fix this is to turn each character into a character:

format s = map (\c -> if punct c then ' ' else c ) s

Another way is to apply intercalate "" (or concat) as you did before to join the list of strings into a single string:

format s = intercalate "" $ map (\c -> if punct c then " " else [c] ) s

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476557

The reason why it is not working is because your map maps Chars on Strings (so [Char]s). As a result the output of the map would be [String]. Nevertheless if you want to delete those above, your function in the map does not throw the chars away.

Furthermore you punct function is wrong. isDigit, etc. are functions. So you call it with isDigit c, not c isDigit:

punct :: Char -> Bool
punct c | c `elem` ['.', ',', '?', '!', ':', ';', '(', ')'] = True 
        | isDigit c = True  
        | isAlpha c = True 
        | otherwise = False

Nevertheless, you can easily make this statement more compact and transform it into:

punct :: Char -> Bool
punct c = isDigit c || isDigit c || c `elem` ['.', ',', '?', '!', ':', ';', '(', ')']

There are two options here: (1) either you do a post processing where you filter out all the punctuations; or (2) in case you delete the punctuations, you actually do a replacement instead of inserting a space.

(1) Post processing:

The post-processing is simply a filter:

post_format :: String -> String
post_format = filter (not . punct)

You better use post processing, if you still want to use the "original" format function.

(1) Replacement:

In that case you could use a helper function:

helper :: Char -> Char
helper x | punct x = ' '
         | otherwise = x

Then format collapses to:

format :: String -> String
format = map helper

Upvotes: 2

Related Questions