Say there is a stadium and the row number is something like A1-10, then B1-10 and so on until ZZ
How do I make a custom data type and use it to represent the seat in Haskell?
You can think of your enumeration as being composed of three parts
The first and second part both rely upon the notion of "letter", so let's define that
data Letter
= La | Lb
| Lc | Ld
| Le | Lf
| Lg | Lh
| Li | Lj
| Lk | Ll
| Lm | Ln
| Lo | Lp
| Lq | Lr
| Ls | Lt
| Lu | Lv
| Lw | Lx
| Ly | Lz
deriving ( Eq, Ord, Show )
This type is explicitly enumerated instead of merely using Char
so that we don't have to worry about the differences between lower and upper case or the problems of Char
containing extra things like '-'
and '^'
. Since I enumerated the elements in alphabetical order, the autoderived instances like Ord
behave properly.
We probably do want to take advantage of the fact that Letter
is a subset of Char
so let's write the projections, too.
-- This one always works since every letter is a character.
letterToChar :: Letter -> Char
letterToChar l = case l of
La -> 'a'
Lb -> 'b'
Lc -> 'c'
Ld -> 'd'
Le -> 'e'
Lf -> 'f'
Lg -> 'g'
Lh -> 'h'
Li -> 'i'
Lj -> 'j'
Lk -> 'k'
Ll -> 'l'
Lm -> 'm'
Ln -> 'n'
Lo -> 'o'
Lp -> 'p'
Lq -> 'q'
Lr -> 'r'
Ls -> 's'
Lt -> 't'
Lu -> 'u'
Lv -> 'v'
Lw -> 'w'
Lx -> 'x'
Ly -> 'y'
Lz -> 'z'
-- This one might fail since some characters aren't letters. We also do
-- automatic case compensation.
charToLetter :: Char -> Maybe Letter
charToLetter c = case Char.toLower of
'a' -> Just La
'b' -> Just Lb
'c' -> Just Lc
'd' -> Just Ld
'e' -> Just Le
'f' -> Just Lf
'g' -> Just Lg
'h' -> Just Lh
'i' -> Just Li
'j' -> Just Lj
'k' -> Just Lk
'l' -> Just Ll
'm' -> Just Lm
'n' -> Just Ln
'o' -> Just Lo
'p' -> Just Lp
'q' -> Just Lq
'r' -> Just Lr
's' -> Just Ls
't' -> Just Lt
'u' -> Just Lu
'v' -> Just Lv
'w' -> Just Lw
'x' -> Just Lx
'y' -> Just Ly
'z' -> Just Lz
_ -> Nothing -- default case, no match
Now we play the same game with "digits from 1 to 10"
data Digit
= D1 | D2
| D3 | D4
| ...
deriving ( Eq, Ord, Show )
digitToInt :: Digit -> Int
digitToInt = ...
intToDigit :: Int -> Maybe Digit
intToDigit = ...
We might even write other ways of retracting an Int
to a Digit
. For instance, we could (1) take the absolute value of the integer and then (2) take its div
and mod
against 10 seats. This will result in a Digit
assignment and a row number.
intToDigitWrap :: Int -> (Int, Digit)
intToDigitWrap n = (row, dig) where
(row, dig0) = n `divMod` 10
-- we use an incomplete pattern match because we have an invariant
-- now that (dig0 + 1) is in [1, 10] so intToDigit always succeeds
Just dig = intToDigit (dig0 + 1)
And the final type is easy!
data Seat = Seat { letter1 :: Letter
, letter2 :: Maybe Letter
, digit :: Digit
} deriving ( Eq, Ord, Show )
The Ord
type is again completely automatically correct as Nothing
is less than Show x
for any x
and record ordering is lexicographic. We can also write a show instance that's a bit friendlier very simply
prettySeat :: Seat -> String
prettySeat s =
let l1 = [Char.toUpper $ letterToChar $ letter1 s]
l2 = case letter2 s of
Nothing -> ""
Just c -> [Char.toUpper $ letterToChar c]
dig = show (digitToInt (digit s))
in l1 ++ l2 ++ "-" ++ dig
In all likelihood the ability to inject the Letter
and Digit
types into their superset types Char
and Int
respectively will almost certainly come in handy when writing code later.
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.