Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell - Couldn't match type `[Char]' with `Char'

Tags:

haskell

I currently have the following code in Haskell

splitStringOnDelimeter :: String -> Char -> [String]

splitStringOnDelimeter "" delimeter = return [""]

splitStringOnDelimeter string delimeter = do
    let split = splitStringOnDelimeter (tail string) delimeter
    if head string == delimeter
    then return ([""] ++ split)
    else return ( [( [(head string)] ++ (head split) )] ++ (tail split))

If I run it in a Haskell terminal (i.e. https://www.tryhaskell.org) with values for the return statement such as ( [( [(head "ZZZZ")] ++ (head ["first", "second", "third"]) )] ++ (tail ["first", "second", "third"])) or [""] ++ ["first", "second", "third"] or [""] then I receive the correct types from the terminal which is different to my local stack compiler. Furthermore, if I also change the top return statement to return "" then it doesn't complain about that statement which I'm pretty sure is incorrect.

My local compiler works fine with the rest of my Haskell codebase which is why I think it might be something wrong with my code...

like image 965
Elliot Smith Avatar asked Dec 13 '22 17:12

Elliot Smith


1 Answers

One of the unfortunate things in the design of the Monad typeclass, is that they introduced a function called return. But although in many imperative programming languages return is a keyword to return content, in Haskell return has a totally different meaning, it does not really return something.

You can solve the problem by dropping the return:

splitStringOnDelimeter :: String -> Char -> [String]
splitStringOnDelimeter "" delimeter = [""]
splitStringOnDelimeter string delimeter =
    let split = splitStringOnDelimeter (tail string) delimeter in
    if head string == delimeter
    then ([""] ++ split)
    else ( [( [(head string)] ++ (head split) )] ++ (tail split))

The return :: Monad m => a -> m a is used to wrap a value (of type a) in a monad. Since here your signature hints about a list, Haskell will assume that you look for the list monad. So that means that you return would wrap [""] into another list, so implicitly with return [""] you would have written (in this context), [[""]], and this of course does not match with [String].

The same goes for do, again you them make a monadic function, but here your function has not much to do with monads.

Note that the name return is not per se bad, but since nearly all imperative languages attach an (almost) equivalent meaning to it, most people assume that it works the same way in functional languages, but it does not.

Mind that you use functions like head, tail, etc. These are usually seen as anti-patterns: you can use pattern matching instead. We can rewrite this to:

splitStringOnDelimeter :: String -> Char -> [String]
splitStringOnDelimeter "" delimeter = [""]
splitStringOnDelimeter (h:t) delimeter | h == delimeter = "" : split
                                       | otherwise = (h : sh) : st
    where split@(sh:st) = splitStringOnDelimeter t delimeter

By using pattern matching, we know for sure that the string has a head h and a tail t, and we can directly use these into the expression. This makes the expression shorter as well as more readable. Although if-then-else clauses are not per se anti-patterns, personally I think guards are syntactically more clean. We thus use a where clause here where we call splitStringOnDelimter t delimeter, and we pattern match this with split (as well as with (sh:st). We know that this will always match, since both the basecase and the inductive case always produce a list with at least one element. This again allows use to write a neat expression where we can use sh and st directly, instead of calling head and tail.

If I test this function locally, I got:

Prelude> splitStringOnDelimeter "foo!bar!!qux" '!'
["foo","bar","","qux"]

As take-away message, I think you better avoid using return, and do, unless you know what this function and keyword (do is a keyword) really mean. In the context of functional programming these have a different meaning.

like image 106
Willem Van Onsem Avatar answered Jan 07 '23 06:01

Willem Van Onsem