Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern matching from a [String] in Haskell

Tags:

list

haskell

I'm following an introductory course on functional programming, where we use Haskell. Part of an excercise is to write a parser for the input string.

However I can't solve the following error, or get what is actually happening.

Parser.hs:29:71:
Couldn't match expected type `String' with actual type `Char'
In the first argument of `readPoint', namely `start'
In the expression: readPoint start
In the expression:
  (readLines track, readPoint start, readLine finish)

The error originates from this line:

readTrack str = parseTrack (lines str) where
    parseTrack (start : finish : track) = (readLines track, readPoint start, readLine finish)

What I expected to happen is that the input string got split into a list of lines, which get passed to parseTrack. parseTrack would then use pattern matching to name the top two strings(lines) from the list and the rest.

However what I believe is happening is that finish is the top element from the list, and start gets assigned the top char from that string.

I would really like to know how to solve this problem and what is actually happening.

Thanks a lot!

Parser.hs

module Parser where

import Types

readFloat :: String -> Float
readFloat str = case reads str of
    [] -> error "not a floating point number"
    (p,_):_ -> p

readInt :: String -> Int
readInt str = case reads str of
    [] -> error "not an integer"
    (p,_):_ -> p

readPoint :: String -> Point
readPoint str = parsePoint (words str) where
    parsePoint (x : y : _) = (readInt x, readInt y)

readLine :: String -> Line
readLine str = parseLine (words str) where
    parseLine (x1 : y1 : x2 : y2 : _) = ((readInt x1, readInt y1), (readInt x2, readInt y2))

readLines :: String -> [Line]
readLines str = parseLines (lines str) where
    parseLines (line : rest) = readLine line : parseLines rest

readTrack :: String -> Track
readTrack str = parseTrack (lines str) where
    parseTrack (start : finish : track) = (readLines track, readPoint start, readLine finish)

Types.hs

module Types where

type Vector2D   = (Int, Int)
type Point      =  Vector2D
type Line       = (Point, Point)
type Velocity   =  Vector2D

type CarState   = (Position, Velocity)
type Position   =  Vector2D
type Trace      = [Position]

type Track      = ([Line], Point, Line)
like image 922
Timo Meijer Avatar asked Feb 20 '23 03:02

Timo Meijer


1 Answers

Your variable track was actually a list of single lines, not a string with '\n's in it. Since you've already split it into lines, you can just map readLine over it, giving:

readTrack str = parseTrack (lines str) where
    parseTrack (start:finish:tracks) 
                     = (map readLine tracks, readPoint start, readLine finish)

Here tracks :: [String], which is why you can map readLine on them all - you don't need to use readLines to split it into lines first. (You can tell it's a list because it's the final thing to the right hand side of a :.)

You say

However what I believe is happening is that finish is the top element from the list, and start gets assigned the top char from that string.

Well what was happening was: because you asked for readLines track as the first output, Haskell started there, and since you declared

readLines :: String -> [Line]

that meant that track had to be a String - that's the only thing readLines can deal with.

First, you need to remember that : has an element on its left and a list on its right, so in

3:4:stuff

stuff has to be [Integer] because it's on the right of some Integer elements. Similarly,

c:"a string"

means c has to be a Char because String = [Char].

In your code, we've worked out that track is a String, so that means that when you write

(start : finish : track)

both start and finish have to be elements you can put at the front of a String, so both start and finish have to be Char.

Haskell then looks at your code readPoint start, but because it's worked out that start has type Char, but

readPoint :: String -> Point

it complains that Char and String don't match.

I think you made the mistake because you forgot that readLines takes a single string, but it felt (from the name) like it should happily take a list of strings. Your parseLines looks like it does a similar thing, but it takes a list of strings, so copes, whereas readLines takes a single string with newline characters, so can't cope with a list.

like image 76
AndrewC Avatar answered Feb 28 '23 07:02

AndrewC