这是一个简单的问题,但我仍然不确定.在Haskell中使用自定义类型的主要好处是什么,例如:
data Users a = User a a a
data Towns a = TownA Int | TownB String | Nill
Run Code Online (Sandbox Code Playgroud)
这些自定义类型的主要用途是作为命令式语言中的对象吗?如
template<typename U>
class User
{
U a1;
U a2;
U a3;
};
int main()
{
User<std::string> UserObj; UserObj.a1="a1"; //etc
}
Run Code Online (Sandbox Code Playgroud)
有没有人有简短而一般的解释?
NB我理解二进制类型的有用性,比如Maybe,我的问题更多的是关于自定义类型的使用.
如果不考虑具体引用的类型[1],我认为我们可以讨论三种主要的好处:
作为一个任意的例子,选择你的Users类型.您可以尝试仅使用列表,而不是定义单独的数据类型.也许您甚至可以使用type提供类型同义词并使其更漂亮:
type Users a = [a]
makeUsers u1 u2 u3 = [u1, u2, u3]
Run Code Online (Sandbox Code Playgroud)
但是,如果您的原始定义是data Users a = Users a a a,那么可以合理地假设数据类型中有三个字段不是任意选择的结果,但之所以如此,是因为每个集合中有三个用户是您想要的相关条件执行.Users但是,如果是列表,则通过更改列表中的元素数来打破它是微不足道的:
us1 = makeUsers "Jack" "Eric" "Ginger"
us2 = drop 1 us1 -- Oops!
Run Code Online (Sandbox Code Playgroud)
但是,如果按照最初的建议定义类型,那么这样的事故就变得不可能了,因为没有办法Users用多于或少于三个字段来创建.通常,自定义类型可以更好地控制您或其他任何人可以使用类型的值执行的操作.其他类似的移动包括不为自定义类型导出构造函数和字段名称(这与在C++之类的字段中使字段具有私有性具有大致相同的效果),而不是为您不想实际使用的类编写实例.
鉴于我们上面所说的,有人可能会考虑制作Users一个3元组 - 毕竟,这可以保证包含三个元素:
type Users a = (a, a, a)
makeUsers u1 u2 u3 = (u1, u2, u3)
Run Code Online (Sandbox Code Playgroud)
这当然是一种改进.但是,假设您想要将函数应用于一个中的所有三个值Users.如果Users是Functor,你可以简单地使用fmap:
us1' = fmap reverse us
Run Code Online (Sandbox Code Playgroud)
但是,没有Functor同构3元组的实例,并且在自己的代码中编写类的实例,当类和类型在其他库中定义时(行话术语是"孤立实例")几乎总是可怕的想法.所以你很难写一个没有类的map函数,它有自己的名字:
mapUsers :: (a -> b) -> Users a -> Users b
mapUsers f (Users u1 u2 u3) = Users (f u1) (f u2) (f u3)
Run Code Online (Sandbox Code Playgroud)
但是,这样做很快就会变老,用很多不需要的名称污染你的命名空间,最糟糕的是,阻止你使用其他库中需要一个实例的所有机器Functor,而不是一些任意类型的碰巧fmap有为它定义的类似函数.但是,如果您有自定义类型,则可以提供实例:
instance Functor Users where
fmap f (Users u1 u2 u3) = Users (f u1) (f u2) (f u3)
Run Code Online (Sandbox Code Playgroud)
(顺便说一句,我应该提一下,该语言中有一个工具,它准确地涵盖了您希望使用预先存在的类型但具有新的或不同的实例的情况:关键字newtype.但这是针对另一个未来的问题. )
最后,使用自定义名称(以及自定义字段名称,如果使用记录语法)的自定义类型的好处使得您使用的每个值实际上意味着更加明显,这肯定是一个非常显着的收益.
正如chi在评论中指出的那样,这些好处适用于任何类型的语言.然而,考虑到Haskell类型系统的强大功能,通过定义自己的类型所带来的优势比大多数其他语言更大.
[1]:虽然如果你实际使用它,Towns a你可能应该删除缺失的值占位符Nill...
data Town = TownA String | TownB Int
Run Code Online (Sandbox Code Playgroud)
...而Maybe Town不是Town在您需要的情况下使用Nill.