简体   繁体   中英

Haskell parsing to custom datatype

I'm relatively new to Haskell. Here is what i want to do:

I want parse strings to chesspieces. my code is pretty straightforward so i hope it will explain itself:

data ChessPiece = King | ... etc
data DraughtPiece = DKing | ... etc
data Player = Black | White deriving (Show, Eq)
data Piece a = Piece (a, Player) 

So a Piece can be of either 2 games, of either 2 players. I want a string "k" or "K" to respectively parse to a Black or White King, so I made this:

class Parse a where
  parse :: String -> a

instance Parse ChessPiece where
  parse a = case a of 
    "k" -> King

Everything fine so far.. i can call > parse "k" :: ChessPiece . this works!

instance Parse a => Parse (Piece a) where
   parse x | isLowercase x = Piece (parse             x, White)
           | otherwise     = Piece (parse $ lowercase x, Black)

This has to work for either piece. The rule for uppercase and lowercase goes for DraughtPiece and for ChessPiece. How do i tell Haskell to parse the x to the correct type (a). Leaving out the cast for parse x gives me non-exhaustive pattern error, changing it to parse x :: a gives me 'could not deduce (Parse a1) from a use of 'parse' from the context (Parse a)'

How do i tell Haskell to parse "K" :: Piece ChessPiece to (parse "k" :: ChessPiece, Black) to (King, Black) ?

How do i tell Haskell to parse the x to the correct type (a).

If you're referring to

instance Parse a => Parse (Piece a) where
   parse x | isLowercase x = Piece (parse             x, White)
           | otherwise     = Piece (parse $ lowercase x, Black)

you don't need to. The type at which the recursive call to parse is made is determined from the context, it's the parameter type a .

But when you want to parse anything, there must be enough context to tell GHC what type the result shall have. In typical programmes, that can usually be determined from the context. If you have let p = parse input and then later use p in a context that requires p to have a certain type, that tells the compiler which type shall be parsed. But at the ghci prompt, there is no such context, and you must explicitly tell ghci what type you want

ghci> parse "K" :: Piece ChessPiece

Leaving out the cast for 'parse x' gives me non-exhaustive pattern error,

If you try to call parse with an input string you have not explicitly handled, you will get a non-exhaustive pattern error. The compilation alone will give you a warning about it (if you ask for warnings), eg

Pieces.hs:14:13: Warning:
    Pattern match(es) are non-exhaustive
    In a case alternative:
        Patterns not matched:
            []
            (GHC.Types.C# #x) : _ with #x `notElem` ['k']
            (GHC.Types.C# 'k') : (_ : _)

which means that in the instance Parse Piece , you only defined parse for the single input string "k" . You should of course provide definitions for other input strings (including an explicit catch-all branch calling error for invalid strings).

changing it to 'parse x :: a' gives me 'could not deduce (Parse a1) from a use of 'parse' from the context (Parse a)

That's a somewhat non-obvious thing. Type variables are implicitly forall-quantified, so when you write

instance Parse a => Parse (Piece a) where
    parse x | isLowercase x = Piece (parse x :: a, White)

the a in the definition of parse is a fresh forall-quantified type variable, and you effectively say that the first component of the pair can have any type, as if you said

instance Parse a => Parse (Piece a) where
    parse x | isLowercase x = Piece (parse x :: anyType, White)

and of course, there is no instance Parse anyType that can be inferred from the context Parse a .

You can tell GHC that the a in the tuple should denote the same type as the one in the instance head by using the ScopedTypeVariables extension, but it's better to leave that for the time being.

You need ScopedTypeVariables pragma to allow use of things like parse x :: a where you have used typevariable a in the function type.

You can also use FlexibleInstances to define instance for Piece ChessPiece and Piece DraughtPiece if you want to something more specific for each case.

instance Parse (Piece ChessPiece) where
   parse x = -- return Piece ChessPiece here 

instance Parse (Piece DraughtPiece) where
   parse x = -- return Piece DrauPiece here 

In any case ghc needs enough context to know what type to parse to so you will need something like parse "k" :: Piece ChessPiece

Since your DraughtPiece is pretty much the same as ChessPiece , I would suggest you put them into the same data type for the same reason you don't invent a new Maybe type every time Nothing seems to be the wrong word . Another bonus is that the code scales much better when you want to increase the amount of games.

data ChessPiece = King | ... etc
data Game = Game Int
data Player = Black | White
data Piece = Piece ChessPiece Player Game

You now have to adjust your data representation. If you can adjust the representation of your file to be parsed, you could encode "black king of board n" as nk .

import Data.Char

instance Parse Piece where
      parse x = case x of
            [n,p] | isLower p -> Piece (parse [p]) White (parse [n])
                  | otherwise -> Piece (parse [p]) Black (parse [n])
            _ -> [Parse error]

instance Parse ChessPiece where
      parse [p] = case toLower p of
            'k' -> King
            ...
            _ -> [Parse error]

instance Parse Game where
      parse = Game . digitToInt

A final note: the main difficulty of your problem is because your data isn't stored atomically: one token contains information about both color and type of a figure. When you're designing your own files, try to keep separate things separate.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM