-3

我已经用谷歌搜索了这件事,但找不到任何相关的东西(让我感到惊讶,因为这似乎是许多人想要尝试的显而易见的事情)。

我想做这样的事情:

data Car = "ford" | "chevy"

换句话说,我希望右侧的值是特定的值,即特定的字符串、特定的数字等,而不是一般的值。

这可能吗?我该怎么做?

谢谢。

编辑:我不是在寻找:数据 Car = Ford | 雪佛兰

4

4 回答 4

8

data Car = Ford | Chevy

数据构造函数不是字符串。请注意,第一个字母的大写不是可选的。在 haskell 中,数据构造函数的第一个字母必须大写,值的标识符必须有非大写的首字母。

编辑:

以下是如何将类型值限制CarFordChevy仍然将它们用作字符串:

data Car = Ford | Chevy

carToString Ford = "Ford"
carToString Chevy = "Chevy"

然后,您将使用类似的东西创建一个值,mycar = Ford并且只要您想将这些值用作字符串,您只需使用carToString mycar.

于 2012-09-24T23:32:02.710 回答
5

您尝试做的事情在 Haskell 中无法直接实现。data Car = "ford" | "chevy"看起来它正在尝试创建String;的子类型 所有Car值都是Strings,但并非所有Strings 都是Cars。您尝试的语法的更一般用法会创建奇怪的类型,这些类型是子类型的无差别联合(例如data Strange = 1 | "one" | '1'.

Haskell 的类型系统没有子类型,所以这永远不会直接工作。

如果您实际上并不关心类型的值CarStrings,那么您可以使用data Car = Ford | Chevy(如 Matt S 所建议的那样)。

如果您确实关心 type 的值CarStrings,大概是因为您希望能够将函数String应用于Car值,但您不希望能够将任何 oldString作为 a传递Car

与创建仅是您想要的 s 的类型不同的方法是创建一个Car类型加上一个函数,该函数为您提供与任何给定的 s 对应的函数。例如:StringCarStringCar

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函数呈现为, 和. 这不完全是您最初拥有的,因为第一个字母是大写的(数据构造函数名称必须以大写字母开头,并且派生实例将匹配构造函数名称)。您可以手动编写自己的实例而不是派生它,如下所示:FordString "Ford"Chevy"Chevy"ShowShow

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
于 2012-09-25T00:39:17.660 回答
4

拥有字符串对您来说似乎非常重要,但您希望拥有类型安全性。字符串本质上不是类型安全的。抽象数据类型类型安全的,一旦你习惯了它,你就会发现你喜欢它。

一个好处是您可以允许用户使用任何大小写(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)。

于 2012-09-25T06:36:05.203 回答
2

目前还不清楚您希望实现什么。听起来您想要一种可以在任何地方使用的类型 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'

但是..这是将文字值转换为类似Integeror的类型的过程Double。没有通用数字类型Integer并且Double是其子类型。

我们可以Car通过使用 quasi-quoter 来做类似的事情,这样你就可以写:

myCar :: Car
myCar = [car| ford |]

这将被接受,但这将被拒绝:

notMyCar :: Car
notMyCar = [car| apple |]

但是现在我们已经真正脱离了深层次,并没有更接近你想要的......虽然你想要的仍然很不清楚。

于 2012-09-25T04:58:21.727 回答