假设有一个体育场,行号类似于 A1-10,然后是 B1-10,依此类推,直到 ZZ
如何创建自定义数据类型并使用它来表示 Haskell 中的席位?
最佳答案
您可以将枚举视为由三部分组成
- 第一个字母,
- (可选)第二个字母,以及
- 1 到 10 之间的数字
第一部分和第二部分都依赖于“字母”的概念,所以让我们定义一下
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 )
这种类型是显式枚举的,而不是仅仅使用Char
,这样我们就不用担心大小写的差异或者Char
包含的问题额外的东西,如 '-'
和 '^'
。由于我按字母顺序枚举元素,因此像 Ord
这样的自动派生实例可以正常运行。
我们可能确实想利用 Letter
是 Char
的子集这一事实,所以我们也编写投影。
-- 这个总是有效,因为每个字母都是一个字符。
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
现在我们用“1到10的数字”玩同一个游戏
data Digit
= D1 | D2
| D3 | D4
| ...
deriving ( Eq, Ord, Show )
digitToInt :: Digit -> Int
digitToInt = ...
intToDigit :: Int -> Maybe Digit
intToDigit = ...
我们甚至可以编写其他方法将 Int
还原为 Digit
。例如,我们可以 (1) 取整数的绝对值,然后 (2) 对 10 个席位取其 div
和 mod
。这将导致Digit
分配和行号。
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)
最终的类型很简单!
data Seat = Seat { letter1 :: Letter
, letter2 :: Maybe Letter
, digit :: Digit
} deriving ( Eq, Ord, Show )
Ord
类型再次完全自动正确,因为对于任何 x
,Nothing
小于 Show x
且记录顺序是按字典顺序排列的。我们还可以非常简单地编写一个更友好的 show 实例
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
很可能能够将 Letter
和 Digit
类型注入(inject)其超集类型 Char
和 Int
稍后编写代码时,几乎肯定会派上用场。
关于Haskell 自定义数据类型和表示,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28874880/