Essentially practically I'm trying to implement a custom data type that is a subset of the string class (e.g. so I can do some type checking for error input). But I cannot for the life of me find anywhere that just explains what each type keyword means. According to here: Haskell Docs these are the 7 basic ones: Eq, Ord, Enum, Ix, Bounded, Read, and Show. And aside from Show which is needed for print statements and Eq which needed for boolean style comparison checks, I'm not completely certain on the other 5, and google has not been very much help. So I'm hoping y'all can shed some light and maybe point me in the correct direction.
So questions:
- What are these 7 basic derives / what does adding them do?
- Is there a way within ghci I can run something like
derives String
orderives "abc"
?
Here's the code I'm working on. Essentially I've just created this Card data type which as you can see is just a string that does some more strict parameter checks. And what I'm trying to do is pass it to match which previously accepted 3 strings as an argument (now 3 Cards). But in order to use the string manipulation syntax like breaking it down into a list of characters, I believe I need to identify the appropriate derive keyword so that it behaves in this manner.
match :: Card -> Card -> Card -> Bool
match [] [] [] = True
match [a] [b] [c] = (a == b && b == c)
|| (a /= b && b /= c && a /= c)
match (a:as) (b:bs) (c:cs) = match [a] [b] [c]
&& match as bs cs
data Card = Card String
deriving (Show,Eq)
card :: String -> Card
card x | length x /= 4 = error "Card input must be 4 characters."
| (x!!0 /= 'o') && (x!!0 /= 's') && (x!!0 /= 'd') = error "char 0 (shape) must be o s or d."
| (x!!1 /= '1') && (x!!1 /= '2') && (x!!1 /= '3') = error "char 1 (number) must be 1 2 or 3."
| (x!!2 /= 'r') && (x!!2 /= 'p') && (x!!2 /= 'g') = error "char 2 (color) must be r p or g."
| (x!!3 /= 'f') && (x!!3 /= 's') && (x!!3 /= 'o') = error "char 3 (shade) must be f s or o."
| otherwise = Card x
(Updated Example #1)
data Card = Card Shape Number Color Shade deriving (Show, Eq)
data Shape = Oval | Squiggle | Diamond deriving Eq
instance Show Shape where
show Oval = "Oval"
show Squiggle = "Squiggle"
show Diamond = "Diamond"
data Number = One | Two | Three deriving Eq
instance Show Number where
show One = "One"
show Two = "Two"
show Three = "Three"
data Color = Red | Pink | Green deriving Eq
instance Show Color where
show Red = "Red"
show Pink = "Pink"
show Green = "Green"
data Shade = Full | Half | Empty deriving Eq
instance Show Shade where
show Full = "Full"
show Half = "Half"
show Empty = "Empty"
shape :: Char -> Either ShapeError Shape
shape 'o' = Right Oval
shape 's' = Right Squiggle
shape 'd' = Right Diamond
shape x = Left (NotOSD x)
number :: Char -> Either NumberError Number
number '1' = Right One
number '2' = Right Two
number '3' = Right Three
number x = Left (Not123 x)
color :: Char -> Either ColorError Color
color 'r' = Right Red
color 'p' = Right Pink
color 'g' = Right Green
color x = Left (NotRPG x)
shade :: Char -> Either ShadeError Shade
shade 'f' = Right Full
shade 's' = Right Half
shade 'o' = Right Empty
shade x = Left (NotFSO x)
card :: String -> Either SetError Card
card [shp, n, c, shd] = Card <$> shape shp <*> number n <*> color c <*> shade shd
card x = Left (NotCard x)
data CardError = NotCard String
instance Show CardError where
show (NotCard x) = "Card must be 4 characters long, not " show x
data SetError = CardError | ShapeError | NumberError | ColorError | ShadeError deriving Eq
data ShapeError = NotOSD Char
instance Show ShapeError where
show (NotOSD x) = "Shape must be o, s, or d, not " show x
data NumberError = Not123 Char
instance Show NumberError where
show (Not123 x) = "Number must be 1, 2, or 3, not " show x
data ColorError = NotRPG Char
instance Show ColorError where
show (NotRPG x) = "Color must be r, p, or g, not " show x
data ShadeError = NotFSO Char
instance Show ShadeError where
show (NotFSO x) = "Shade must be f, s, or o, not " show x
Current Error
match.hs:84:34:
Couldn't match type ‘ShapeError’ with ‘SetError’
Expected type: Either SetError Shape
Actual type: Either ShapeError Shape
In the second argument of ‘(<$>)’, namely ‘shape shp’
In the first argument of ‘(<*>)’, namely ‘Card <$> shape shp’
match.hs:84:48:
Couldn't match type ‘NumberError’ with ‘SetError’
Expected type: Either SetError Number
Actual type: Either NumberError Number
In the second argument of ‘(<*>)’, namely ‘number n’
In the first argument of ‘(<*>)’, namely
‘Card <$> shape shp <*> number n’
match.hs:84:61:
Couldn't match type ‘ColorError’ with ‘SetError’
Expected type: Either SetError Color
Actual type: Either ColorError Color
In the second argument of ‘(<*>)’, namely ‘color c’
In the first argument of ‘(<*>)’, namely
‘Card <$> shape shp <*> number n <*> color c’
match.hs:84:73:
Couldn't match type ‘ShadeError’ with ‘SetError’
Expected type: Either SetError Shade
Actual type: Either ShadeError Shade
In the second argument of ‘(<*>)’, namely ‘shade shd’
In the expression:
Card <$> shape shp <*> number n <*> color c <*> shade shd
match.hs:85:16:
Couldn't match expected type ‘SetError’
with actual type ‘CardError’
In the first argument of ‘Left’, namely ‘(NotCard x)’
In the expression: Left (NotCard x)
Failed, modules loaded: none.
CodePudding user response:
When writing Haskell, don't be afraid of defining lots of types and lots of small functions. Small functions are easier to understand, and can be combined in various ways to define larger functions.
Do you care about the typeclasses...
To get your immediately question out of the way, the Show
instance is only used for producing a String
from some value. Eq
is used to allow comparing two values of the same type with ==
. The other classes you can derive instances for (without GHC-specific extensions) are
Read
Ord
Enum
Bounded
Read
is like the inverse of Show
, but is generally not used. (The parsers I show below are easier to write than Read
instances, and you won't be able to make use of derived Read
instances for most of the types you'll define.) The other three may be relevant to other parts of your program, but not to any of the code in your question.
Derived Show
instances basically just convert the data constructor name to a string. Derived Eq
instances just check if the two data constructors are the same or not. Definitions for data constructors that take arguments are derived recursively: show (Card x)
would be defined using "Card"
and show x
, while Card c1 == Card c2
would be define as c1 == c2
.
Most of your question, though, is about pattern matching (as mentioned in the comments), which has nothing to do with type classes.
...or the types?
Let's start with your four basic concepts: shape, number, color, and shade. Define a separate type for each of them. Here's the type for Shape
as an example:
data Shape = ShapeO | ShapeS | ShapeD deriving Eq
instance Show Shape where
show ShapeO = "O"
show ShapeS = "S"
show ShapeD = "D"
Use descriptive names for data constructors where possible (is O
really short for Oval
? If so, use Oval
), and keep in mind that the names must be globally unique (within a module, anyway), so you can't us O
for both Shape
and Shade
. Sometimes the strings derived from the constructor names will be sufficient for your Show
instance sometimes not.
Whether you deriving the Show
instance or write it manually probably depends on the data constructor names you choose. The derived Eq
instances will almost certainly be sufficient, as they do not depend on the names you use for constructor.
(As an aside, Number
is tricky because you can't use 1
, 2
, or 3
as data constructor names. Unless you need to do math with the numbers, you might consider something like data Number = One | Two | Three
instead of the overly broad data Number = Number Int
.)
Now your Card
type can a simple composition of your four attribute types, not just a wrapper around the raw string.
data Card = Card Shape Number Color Shade deriving (Show, Eq)
Parsing: strings to values
You'll want functions that parse a character into an appropriate value, not just check if it could represent an appropriate value. Sometimes this is possible ('o'
for ShapeO
), sometimes it is not ('x'
does not represent a Shape
. So your parser should account for this, but not by simply raising an exception with error
. Instead, let the return type tell the caller what happened, and let them decide whether the program needs to be terminated. For example,
shape :: Char -> Either String Shape
shape 'o' = Right ShapeO
shape 's' = Right ShapeS
shape 'd' = Right ShapeD
shape x = Left $ "Shape must be o, s, or d, got show x
The fact that shape
returns a Right
or Left
value is often enough information for the caller to decide what to do next; the error message wrapped by Left
is more for reporting errors to the user.
Once you have all the parsers for the individual attributes, the parser for a Card
becomes almost trivial, thanks to the Applicative
instance for Either String
types.
card :: String -> Either String Card
card [shp, n, c, shd] = Card <$> shape shp <*> number n <*> color c <*> shade shd
card x = Left $ "Card must e 4 characters long, got " show x
If any of the attributes produce a Left
value, card
will return the Left
value of the first attribute that fails to parse. Only when the input string has exactly 4 of the correct characters will char
produce a Card
value wrapped with Right
.
You might decide to define dedicated error types as well, rather than using arbitrary strings. For example,
data ShapeError = NotOSD Char
instance Show ShapeError where
show (NotOSD x) = "Shape must be o, s, or d, not " show x
then
shape :: String -> Either ShapeError Shape
shape 'o' = Right ShapeO
shape 's' = Right ShapeS
shape 'd' = Right ShapeD
shape x = Left (NotOSD x)
Basically, capture the information about the error in the error type, and let its Show
instance produce a human-readable string with that information if needed.
Things are equal if their parts are equal
match
is similarly trivial, making use of the Eq
instance derived for Card
, which is derived from the Eq
instances for all the attribute types.
match :: Card -> Card -> Card -> Bool
match c1 c2 c3 = c1 == c2 && c2 == c3 || (c1 /= c2 && c2 /= c3 && c3 /= c1)