我已经用谷歌搜索了这件事,但找不到任何相关的东西(让我感到惊讶,因为这似乎是许多人想要尝试的显而易见的事情)。
我想做这样的事情:
data Car = "ford" | "chevy"
换句话说,我希望右侧的值是特定的值,即特定的字符串、特定的数字等,而不是一般的值。
这可能吗?我该怎么做?
谢谢。
编辑:我不是在寻找:数据 Car = Ford | 雪佛兰
data Car = Ford | Chevy
数据构造函数不是字符串。请注意,第一个字母的大写不是可选的。在 haskell 中,数据构造函数的第一个字母必须大写,值的标识符必须有非大写的首字母。
编辑:
以下是如何将类型值限制Car
为Ford
并Chevy
仍然将它们用作字符串:
data Car = Ford | Chevy
carToString Ford = "Ford"
carToString Chevy = "Chevy"
然后,您将使用类似的东西创建一个值,mycar = Ford
并且只要您想将这些值用作字符串,您只需使用carToString mycar
.
您尝试做的事情在 Haskell 中无法直接实现。data Car = "ford" | "chevy"
看起来它正在尝试创建String
;的子类型 所有Car
值都是String
s,但并非所有String
s 都是Car
s。您尝试的语法的更一般用法会创建奇怪的类型,这些类型是子类型的无差别联合(例如data Strange = 1 | "one" | '1'
.
Haskell 的类型系统没有子类型,所以这永远不会直接工作。
如果您实际上并不关心类型的值Car
是String
s,那么您可以使用data Car = Ford | Chevy
(如 Matt S 所建议的那样)。
如果您确实关心 type 的值Car
是String
s,大概是因为您希望能够将函数String
应用于Car
值,但您不希望能够将任何 oldString
作为 a传递Car
。
与创建仅是您想要的 s 的类型不同的方法是创建一个Car
类型加上一个函数,该函数为您提供与任何给定的 s 对应的函数。例如:String
Car
String
Car
data Car = Ford | Chevy
carStr :: Car -> String
carStr Ford = "ford"
carStr Chevy = "chevy"
然后您可以传递Car
值并对Car
它们进行操作,并且当您实际需要该值时String
(例如用于打印),您只需调用carStr
它即可。
但是,有Show
可以转换为 a 的类型的类String
,您可以自动获取如下实例Show
:
data Car = Ford | Chevy
deriving Show
然后该函数会将构造show
函数呈现为, 和. 这不完全是您最初拥有的,因为第一个字母是大写的(数据构造函数名称必须以大写字母开头,并且派生实例将匹配构造函数名称)。您可以手动编写自己的实例而不是派生它,如下所示:Ford
String
"Ford"
Chevy
"Chevy"
Show
Show
data Car = Ford | Chevy
instance Show Car where
show Ford = "ford"
show Chevy = "chevy"
但是由于这么多Show
实例为它们呈现的值生成了完全的 Haskell 语法,我倾向于认为最好不要在可能的情况下偏离这一点。但是您可以两全其美,并拥有一个自动派生的Show
实例,因此您不必手动设置数据构造函数和它们生成的字符串之间的对应关系,以及生成您想要的特定字符串的不同函数:
import Data.Char
data Car = Ford | Chevy
deriving Show
carStr :: Car -> String
carStr = map toLower . show
拥有字符串对您来说似乎非常重要,但您希望拥有类型安全性。字符串本质上不是类型安全的。抽象数据类型是类型安全的,一旦你习惯了它,你就会发现你喜欢它。
一个好处是您可以允许用户使用任何大小写(16 或 32 种可能性),但在内部只有一种表示形式,因此一旦您一次读取数据,使用数据就会快速高效。如果您在内部使用字符串,那么每次您的程序依赖于所使用的 Car 时,您将永远进行更复杂的大小写字符串检查。
如果只在`Car
内部使用数据类型,则根本不需要字符串,因此拥有字符串的唯一原因是处理输入和输出。这是一种具有类型安全性并使用字符串处理输入/输出的方法。编译后,抽象数据类型的数据表示非常小,因此运行速度比字符串快。
data Car = Ford | Chevy
deriving (Read,Show,Eq)
所以现在我们可以读取、显示和检查是否相等。这使您有能力做
read "Ford" -- gives Ford
show Chevy -- gives "Chevy"
Ford == Chevy -- gives False
read "GM" -- gives an exception. oops.
但也许对你来说比阅读和展示更有用的是
getCar :: String -> Maybe Car
getCar xs = case toLower xs of
"ford" -> Just Ford
"chevy" -> Just Chevy
_ -> Nothing
这很棒,因为您可以轻松地使用它进行错误检查,而不会引发异常并使整个程序崩溃,并且您的用户可以使用他们想要的任何大小写。对于一个简单的例子,你可以写
feedback :: Maybe Car -> String
feedback Nothing = "Please enter a valid car. Why not use one of America's favourite brands? Ford or Chevy"
feedback (Just Ford) = "Thanks for choosing Ford."
feedback (Just Chevy) = "Thanks for choosing Chevy"
你还需要
ungetCar :: Car -> String
ungetCar Ford = "ford" -- or you could use "Ford"
ungetCar Chevy = "chevy" -- or you could use "Chevy"
无论您使用"ford"
还是"Ford"
由您决定,但由于我没有在内部使用这个字符串来检查输出,所以我不妨使用普通文本中的大写"Ford"
,所以我可以写
ungetCar = show
现在在您的代码中,您可以编写
... if car == Ford then .... else .....
代替
... if (map toLower carString) == "ford" then ... else
类型安全的好处是,如果你的函数使用Car
数据类型,它可以毫无问题地假设用户输入了一辆有效的汽车,福特或雪佛兰,并且不会由于不是“福特”的字符串而崩溃或出错或“雪佛兰”。这会将您所有的错误检查代码移至您第一次使用getCar
. 如果你使用字符串,并且你希望你的代码是健壮的,那么每个使用汽车的函数都应该检查它给出的汽车是否有效,如果不是就抛出异常,然后你就浪费了很多处理器周期,你在错误处理面条汤。data Car = Ford | Chevy
安全、快捷、方便。
使用抽象数据类型允许您进行防御性编程,同时还可以只检查一次是否有无效数据。仅凭这一点,您应该更喜欢它。它也清晰、干净和容易。
如果您稍后添加另一辆车
数据汽车 = 福特 | 雪佛兰 | 通用汽车
您只需更新几位代码(例如getCar
)。
目前还不清楚您希望实现什么。听起来您想要一种可以在任何地方使用的类型 aString
可以,但同时,它只能是 String 可以是的可能值的子集。但是..这到底是什么意思?
例如,是否应该允许这两种功能?
reverseCar :: Car -> Car
reverseCar = reverse
idCar :: Car -> Car
idCar = map id
通过检查,您可以看到reverseCar
将产生一个不再是真正 a 的输出值Car
。但idChar
实际上根本不会改变任何东西,所以输出仍然是真正的Car
. 但是构建一个可以自动检查的编译器是不可能的。
因此,不可能有一个可以在任何地方使用的类型,String
同时阻止您应用可以工作String
但会导致非Car
值的函数。
除了:
data Char = Ford | Chevy
您的选择是使用类型别名和一些辅助函数:
type Car = String
ford :: Car
ford = "ford"
chevy :: Car
chevy = "chevy"
这将防止您出现拼写错误frod
,并允许您在任何可以使用的Car
地方使用 a。String
但这意味着您也可以应用reverse
创建无效Car
类型的函数。
您可以更进一步并使用新类型包装器:
newtype Car = Car { carToString :: String }
ford :: Car
ford = Car "ford"
chevy :: Char
chevy = Car "chevy"
然后,您可以定义一堆可以安全使用 a 的受信任函数,Car
并且只导出这些函数,而不是Car
数据构造函数。这将阻止人们reverse
直接将函数应用于Car
. 相反,他们将不得不使用carToString
来告诉编译器他们不再想将自己限制在Car
安全功能上。
这也可能不是您想要的。但我认为你越是试图解释你到底想要什么……你就越会意识到你想要的东西是不一致的和/或不可能的。
不可能,我的意思不是“在 Haskell 中不可能”,而是“你需要解决不可能的停机问题”。
您给出的一个示例是Integer
不允许使用文字 3.567。那是混淆了不同的问题。Integer
数据类型本身无法表达 3.567 。它看起来像:
data Integer = I Int32 | J [Digit]
也就是说..它要么是一个可以用机器int表示的小整数,要么是一个数字列表。因此,类型本身明确禁止使用小数点。很像
data Car = Ford | Chevy
阻止你表达“苹果”。
还有将源代码中的数字文字转换为Integer
值的问题。当您编写 (3.567 :: Integer) 时,它本质上是简写,它会扩展为:
(fromRational (3567 % 100)) :: Integer
你会得到一个错误,比如:
No instance for (Fractional Integer)
arising from the literal `3.567'
但是..这是将文字值转换为类似Integer
or的类型的过程Double
。没有通用数字类型Integer
并且Double
是其子类型。
我们可以Car
通过使用 quasi-quoter 来做类似的事情,这样你就可以写:
myCar :: Car
myCar = [car| ford |]
这将被接受,但这将被拒绝:
notMyCar :: Car
notMyCar = [car| apple |]
但是现在我们已经真正脱离了深层次,并没有更接近你想要的......虽然你想要的仍然很不清楚。