Haskell 自定义数据类型和表示

标签 haskell

假设有一个体育场,行号类似于 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 这样的自动派生实例可以正常运行。

我们可能确实想利用 LetterChar 的子集这一事实,所以我们也编写投影。

-- 这个总是有效,因为每个字母都是一个字符。

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 个席位取其 divmod 。这将导致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 类型再次完全自动正确,因为对于任何 xNothing 小于 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

很可能能够将 LetterDigit 类型注入(inject)其超集类型 CharInt稍后编写代码时,几乎肯定会派上用场。

关于Haskell 自定义数据类型和表示,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28874880/

相关文章:

haskell - 如何连接两个 Haskell IO monad

haskell - IO monad 中的函数组合

string - 如何在 Haskell 中替换字符串中的多个字符?

haskell - 当我在输入时遇到解析错误时,如何在 Haskell 中使用 (@) 运算符?

string - Haskell中如何根据字符串解析的结果返回多态类型?

haskell - 在应用函数 monad 之前对输入执行转换

haskell - 有效地解释抽象语法图

debugging - Haskell 将跟踪重定向到一个文件

haskell - 类型系列 : top level vs. 相关联

haskell - 请求澄清 McBride/Paterson Applicative 论文中的转置示例