Dan Hessler
Dan Hessler

Reputation: 390

Variable assignment in haskell

I'm trying to write a helper function that takes in a string and if the string has multiple spaces it turns it into just one string. Here is the code:

getSpaces :: String -> String
getSpaces index = 
    if (length index) == 1 then index
    else
        if isInfixOf " " index then index = " "
        else index

When I try to load my module into GHCI I get the error:

Prelude> :l Utilities
[1 of 1] Compiling Utilities        ( Utilities.hs, interpreted )

Utilities.hs:55:42: error:
parse error on input ‘=’
Perhaps you need a 'let' in a 'do' block?
e.g. 'let x = 5' instead of 'x = 5'
   |
55 |        if isInfixOf " " index then index = " "
   |                                          ^
Failed, no modules loaded.

Why am I not able to reassign index to a single space?

Upvotes: 0

Views: 804

Answers (2)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477641

Why am I not able to reassign index to a single space?

Because in Haskell you are unable to reassign anything. In fact you never even assign, you declare (that is something related, but different). Even in some cases where it might perhaps look like you reassign variables (like in do blocks, you do not reassign, but you shadow variables by declaring a new one with the same name, again this is a bit related, but there are a lot of cases where we see different behavior, for example in a loop, we can not shadow the variable every iteration).

To answer your question, you probably want to return a string with one space, we can do that by writing:

getSpaces index = if (length index) == 1 then index else
                                 if isInfixOf " " index then " " else index

But nevertheless this is still rather inelegant, as well as it is rather unsafe since in case of an infinite String, this will start looping: the length keeps looping until the list (of characters) is exhausted, and it is possible that this never happens. Furthermore even if it does not get stuck in an infinite loop, then still it is not advisable, since it runs in O(n) (with n the length of the list). So for long lists, this is inefficient.

Usually in Haskell, one uses a combination of patterns and guards to distinct between possible values. Your case can be mapped to:

getSpaces :: String -> String
getSpaces st@[_] = st
getSpaces st | isInfixOf " " st = st
             | otherwise = " "

This will still loop however, in case the list has infinite size, sinze isInfixOf keeps looking for a space, until it has found one, or the String is exhausted.

Since isPrefixOf with as search pattern a string with one character in fact means that we look if the string contains a space, we can replace that with elem and look for the character ' ':

getSpaces :: String -> String
getSpaces st@[_] = st
getSpaces st | elem ' ' st = st
             | otherwise = " "

Upvotes: 8

Dan Hessler
Dan Hessler

Reputation: 390

I found a fix, instead of changing index, I am just returning a single space.

Insead of:

if isInfixOf " " index then index = " "

I did:

if isInfixOf " " index then " "

So I just return the space.

Upvotes: 1

Related Questions