我正在尝试用 Haskell 表示汽车牌照。车牌格式定义如下:
旧车牌长度为 5 位字母数字
新牌照由以下部分组成:
我对解决方案的尝试如下
import Data.Char
data Locality = N | S | E | W | C
data Reg = OldReg {code :: String} | NewReg {loc :: Locality,
district :: Int,
month :: Int,
year :: Int,
random:: String}
currentYear =2020
createNewReg::Localitly->Int->Int->Int->String-> Maybe Reg
createNewReg l d m y r
| (d < 1) ||(d > 20) = Nothing --district must be between 1 and 20
| (m < 1) || (m > 12) = Nothing -- month must be between 1 and 12
| (y >= 2020) && y<= currentYear = Nothing -- Year must be after new plates introduce and below or equal to current year
| length(r) /= 3 = Nothing -- random sequence must be 3 digits long
| not $foldl (&&) True $ map Data.Char.isAlpha r = Nothing --random sequence must be alphanumeric
| otherwise = Just $NewReg l d m y r
Run Code Online (Sandbox Code Playgroud)
我想知道是否有更“Haskell”的解决方案来解决这个问题?我可以制作一种限制这些字段的数据类型,而不必使用不同的函数来构造一个吗?
喜欢NewData {random :: String (Alpha :3)}将其限制为长度为 3 的字母数字字符串?
(我知道这不会接近正确的语法,但我希望你能明白)
您可以使用各种奇特的功能扩展 Haskell,这些功能可能会让您有所收获,但也有一些容易实现的成果。正如编程中的典型情况一样,这些通常以权衡的形式出现。这也是这里的情况。
程序员经常使用整数来表示数字,即使这些数字更像是标签或 ID 而不是数字。如果您不对数字进行算术运算,则它们是和类型的候选对象。
例如,您可以这样定义月份:
data Month =
Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec
deriving (Eq, Show, Ord, Enum, Bounded)
Run Code Online (Sandbox Code Playgroud)
这比使用 an 更类型安全,Int因为您仅限于这十二个值。你也很好地回避了是否0或1表示一月等问题。
您可以对地区值(此处未显示)执行相同操作。
同样,如果您知道总是有三个值,则列表不是最好的数据类型。元组更好。
Reg那么,数据类型的部分改进将是这样的:
data Reg =
OldReg { code :: String }
| NewReg { loc :: Locality,
district :: Int,
month :: Month,
year :: Int,
random :: (Char, Char, Char) }
deriving (Eq, Show)
Run Code Online (Sandbox Code Playgroud)
这并不能解决所有问题,因为random仍然可以填充,例如,('1', '2', '2')。想出正确约束年份的类型也更加困难,因此您可能仍然需要智能构造函数。
不过,您可能可以做得更多。通常,车牌只允许大写字母。智能构造函数可以检查小写字母并拒绝它们或转换它们,或者您可以将字母建模为具有 26 个值的和类型:A | B | C | D | ...等等。