Haskell中用户定义的特定值类型?

时间:2012-09-24 23:22:35

标签: haskell types

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

我想做这样的事情:

data Car = "ford" | "chevy"

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

这可能吗?我该怎么做?

感谢。

编辑:我不是在找:数据车=福特|雪佛兰

4 个答案:

答案 0 :(得分:8)

data Car = Ford | Chevy

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

编辑:

以下是如何将Car类型的值限制为FordChevy,并仍然将它们用作字符串:

data Car = Ford | Chevy

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

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

答案 1 :(得分:5)

你想要做的事情在Haskell中是不可能的。 data Car = "ford" | "chevy"看起来正在尝试创建String的子类型;所有Car值都是String s,但并非所有String都是Car s。您正在尝试的语法的更一般用法会创建奇怪的类型,这些类型是不受歧视的子类型联合(例如data Strange = 1 | "one" | '1'

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

如果您实际上并不关心类型Car的值是String s,那么您可以使用data Car = Ford | Chevy(由Matt S提出)。

如果您确实关心类型Car的值是String s,那么这可能是因为您希望能够应用将String转换为Car值的函数,但您不希望能够将任何旧的String作为Car传递。

通过创建{strong 1 仅<{1}}类型的Car类型来实现这一目标的另一种方法是创建String类型和函数这会为您提供与任何给定Car相对应的String。例如:

Car

然后你可以传递data Car = Ford | Chevy carStr :: Car -> String carStr Ford = "ford" carStr Chevy = "chevy" 个值并对它们进行Car个操作,每当你真正需要这个值为Car时(例如打印),你只需调用{{ 1}}就可以了。

但是String类可以转换为carStr类型,您可以自动获取Show的实例:

String

然后,Show函数会将data Car = Ford | Chevy deriving Show 构造函数呈现为show Ford,将String呈现为"Ford"。这不完全是你原来的,因为第一个字母是大写的(数据构造函数名称必须以大写字母开头,派生的Chevy实例将匹配构造函数名称)。您可以手动编写自己的"Chevy"实例而不是派生它,如下所示:

Show

但是由于这么多Show个实例为它们呈现的值产生了完全Haskell语法,我倾向于认为最好不要在可能的情况下脱离这个。但是你可以充分利用这两个世界,并拥有一个自动派生的data Car = Ford | Chevy instance Show Car where show Ford = "ford" show Chevy = "chevy" 实例,这样你就不必手动设置数据构造函数与它们生成的字符串之间的对应关系, plus 一个产生你想要的特定字符串的不同函数:

Show

答案 2 :(得分:4)

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

一个好处是,您可以允许用户使用任何大小写(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具有安全性,速度和便利性。

使用抽象数据类型可以让您进行防御性编程,同时还可以检查一次无效数据。仅此一点,你应该更喜欢它。它也清晰,简洁。

如果您以后添加另一辆车

数据Car = Ford |雪佛兰| GM

你只需更新几个代码(例如getCar)。

答案 3 :(得分:2)

您希望实现的目标并不十分清楚。听起来你想要一个可以在String的任何地方使用的类型,但同时,它只能是String可能的值的子集。但是......这究竟意味着什么?

例如,是否应该允许这两个功能?

reverseCar :: Car -> Car
reverseCar = reverse

idCar :: Car -> Car
idCar = map id

通过检查,您可以看到reverseCar将生成的输出值不再是Car。但是idChar实际上根本不会改变任何东西,所以输出仍然是Car。但是构建一个可以自动检查的编译器是不可能的。

因此,不可能在String处可以使用可以使用的类型,同时阻止您应用适用于String但会导致非Car的函数data Char = Ford | Chevy 值。

除了:

type Car = String

ford :: Car
ford = "ford"

chevy :: Car
chevy = "chevy"

您的选项使用类型别名和一些辅助函数:

frod

这会阻止您制作Car之类的拼写错误,并允许您在任何可以使用String的地方使用reverse。但这意味着您还可以应用Car这样的函数来创建无效的newtype Car = Car { carToString :: String } ford :: Car ford = Car "ford" chevy :: Char chevy = Car "chevy" 类型。

您可以更进一步并使用newtype包装器:

Car

然后,您可以定义一组可靠的函数,这些函数可以安全地使用Car并且只导出这些函数,但不能导出reverse数据构造函数。这会阻止人们将Car等函数直接应用于carToString。相反,他们必须使用Car告诉编译他们不再希望将自己限制在Integer安全功能。

这可能不是你想要的。但我认为你越是试图解释你想要的究竟是什么......你就越会意识到你想要的是不一致和/或不可能的。

不可能,我并不是说“在Haskell中不可能”,而是“你需要解决暂停问题”。

您提供的示例是Integer不允许使用文字3.567。这混合了不同的问题。 data Integer = I Int32 | J [Digit] 数据类型本身无法表达3.567。它看起来像:

data Car = Ford | Chevy

那是..它是一个可以用机器int表示的小整数,或者是一个数字列表。因此,类型本身明确禁止使用小数点。很像

Integer

阻止您表达“苹果”。

还存在将源代码中的数字文字转换为(fromRational (3567 % 100)) :: Integer 值的问题。当您编写(3.567 :: Integer)时,它本质上是简写,扩展为类似:

No instance for (Fractional Integer)
      arising from the literal `3.567'

你得到的错误如下:

Integer

但是..这是将文字值转换为DoubleInteger类型的过程。没有DoubleCar

的子类型的通用数字类型

我们可以使用准引号为myCar :: Car myCar = [car| ford |] 执行类似操作,以便您可以写:

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

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

{{1}}

但是现在我们真的离开了深渊,并没有更接近你想要的东西......虽然你想要的东西仍然很不清楚。