0
votes

I've just started trying to learn haskell and functional programming. I'm trying to write this function that will convert a binary string into its decimal equivalent. Please could someone point out why I am constantly getting the error:

"BinToDecimal.hs":19 - Syntax error in expression (unexpected `}', possibly due to bad layout)

module BinToDecimal where

 total :: [Integer]
 total = []

 binToDecimal :: String -> Integer
 binToDecimal a = if (null a) then (sum total) 
                  else if (head a == "0") then binToDecimal (tail a) 
                       else if (head a == "1") then total ++ (2^((length a)-1))
                                                    binToDecimal (tail a)
3

3 Answers

2
votes

So, total may not be doing what you think it is. total isn't a mutable variable that you're changing, it will always be the empty list []. I think your function should include another parameter for the list you're building up. I would implement this by having binToDecimal call a helper function with the starting case of an empty list, like so:

binToDecimal :: String -> Integer
binToDecimal s = binToDecimal' s []

binToDecimal' :: String -> [Integer] -> Integer
-- implement binToDecimal' here

In addition to what @Sibi has said, I would highly recommend using pattern matching rather than nested if-else. For example, I'd implement the base case of binToDecimal' like so:

binToDecimal' :: String -> [Integer] -> Integer
binToDecimal' "" total = sum total -- when the first argument is the empty string, just sum total. Equivalent to `if (null a) then (sum total)`
-- Include other pattern matching statements here to handle your other if/else cases

If you think it'd be helpful, I can provide the full implementation of this function instead of giving tips.

1
votes

Ok, let me give you hints to get you started:

  • You cannot do head a == "0" because "0" is String. Since the type of a is [Char], the type of head a is Char and you have to compare it with an Char. You can solve it using head a == '0'. Note that "0" and '0' are different.
  • Similarly, rectify your type error in head a == "1"
  • This won't typecheck: total ++ (2^((length a)-1)) because the type of total is [Integer] and the type of (2^((length a)-1)) is Integer. For the function ++ to typecheck both arguments passed to it should be list of the same type.
  • You are possible missing an else block at last. (before the code binToDecimal (tail a))

That being said, instead of using nested if else expression, try to use guards as they will increase the readability greatly.

1
votes

There are many things we can improve here (but no worries, this is perfectly normal in the beginning, there is so much to learn when we start Haskell!!!).

First of all, a string is definitely not an appropriate way to represent a binary, because nothing prevents us to write "éaldkgjasdg" in place of a proper binary. So, the first thing is to define our binary type:

data Binary = Zero | One deriving (Show)

We just say that it can be Zero or One. The deriving (Show) will allow us to have the result displayed when run in GHCI.

In Haskell to solve problem we tend to start with a more general case to dive then in our particular case. The thing we need here is a function with an additional argument which holds the total. Note the use of pattern matching instead of ifs which makes the function easier to read.

binToDecimalAcc :: [Binary] -> Integer -> Integer
binToDecimalAcc [] acc        = acc
binToDecimalAcc (Zero:xs) acc = binToDecimalAcc xs acc
binToDecimalAcc (One:xs) acc  = binToDecimalAcc xs $ acc + 2^(length xs)

Finally, since we want only to have to pass a single parameter we define or specific function where the acc value is 0:

binToDecimal :: [Binary] -> Integer
binToDecimal binaries = binToDecimalAcc binaries 0

We can run a test in GHCI:

test1 = binToDecimal [One, Zero, One, Zero, One, Zero]
> 42

OK, all fine, but what if you really need to convert a string to a decimal? Then, we need a function able to convert this string to a binary. The problem as seen above is that not all strings are proper binaries. To handle this, we will need to report some sort of error. The solution I will use here is very common in Haskell: it is to use "Maybe". If the string is correct, it will return "Just result" else it will return "Nothing". Let's see that in practice!

The first function we will write is to convert a char to a binary. As discussed above, Nothing represents an error.

charToBinary :: Char -> Maybe Binary
charToBinary '0' = Just Zero
charToBinary '1' = Just One
charToBinary _   = Nothing

Then, we can write a function for a whole string (which is a list of Char). So [Char] is equivalent to String. I used it here to make clearer that we are dealing with a list.

stringToBinary :: [Char] -> Maybe [Binary]
stringToBinary []    = Just []
stringToBinary chars = mapM charToBinary chars

The function mapM is a kind of variation of map which acts on monads (Maybe is actually a monad). To learn about monads I recommend reading Learn You a Haskell for Great Good! http://learnyouahaskell.com/a-fistful-of-monads

We can notice once more that if there are any errors, Nothing will be returned.

A dedicated function to convert strings holding binaries can now be written.

binStringToDecimal :: [Char] -> Maybe Integer
binStringToDecimal = fmap binToDecimal . stringToBinary

The use of the "." function allow us to define this function as an equality with another function, so we do not need to mention the parameter (point free notation).

The fmap function allow us to run binToDecimal (which expect a [Binary] as argument) on the return of stringToBinary (which is of type "Maybe [Binary]"). Once again, Learn you a Haskell... is a very good reference to learn more about fmap: http://learnyouahaskell.com/functors-applicative-functors-and-monoids

Now, we can run a second test:

test2 = binStringToDecimal "101010"
> Just 42

And finally, we can test our error handling system with a mistake in the string:

test3 = binStringToDecimal "102010"
> Nothing