自定义数据类型的用途?

时间:2016-10-13 08:33:45

标签: haskell types

这是一个简单的问题,但我仍然不确定。在Haskell中使用自定义类型的主要好处是什么,例如:

data Users a = User a a a 
data Towns a = TownA Int | TownB String | Nill

这些自定义类型的主要用途是在命令式语言中使用对象吗?如

template<typename U>
class User 
{
  U a1;
  U a2;
  U a3;
};

int main()
{
  User<std::string> UserObj; UserObj.a1="a1"; //etc
}

有没有人有简短而一般的解释?

N.B我理解像Maybe这样的二进制类型的用处,我的问题更多的是关于自定义类型的使用。

1 个答案:

答案 0 :(得分:6)

如果不考虑具体引用的类型[1],我认为我们可以讨论三种主要的好处:

限制权力

作为一个任意示例,请选择Users类型。您可以尝试仅使用列表,而不是定义单独的数据类型。也许您甚至可以使用type来提供类型同义词并使其更漂亮:

type Users a = [a]
makeUsers u1 u2 u3 = [u1, u2, u3]

但是,如果您的原始定义是data Users a = Users a a a,则可以合理地假设数据类型中有三个字段不是任意选择的结果,但是之所以这样,是因为存在三个字段每组中的用户是您要强制执行的相关条件。但是,如果Users是一个列表,那么通过更改列表中的元素数来打破它是微不足道的:

us1 = makeUsers "Jack" "Eric" "Ginger"
us2 = drop 1 us1 -- Oops!

但是,如果您按照最初提议的方式定义类型,则此类事故变得不可能,因为无法创建具有多于或少于三个字段的Users。通常,自定义类型可以更好地控制您或其他任何人可以对类型的值执行的操作。其他类似的动作包括不为自定义类型导出构造函数和字段名称(这与使用C ++之类的字段使字段具有大致相同的效果),而不是为您不想实际使用的类编写实例。

增加你的力量

鉴于我们上面所说的,有人可能会考虑让Users成为一个三元组 - 毕竟,这可以保证包含三个元素:

type Users a = (a, a, a)
makeUsers u1 u2 u3 = (u1, u2, u3)

这当然是一种改进。但是,无论出于何种原因,我们都希望将函数应用于Users中的所有三个值。如果UsersFunctor,则只需使用fmap

us1' = fmap reverse us

但是,同构3元组没有Functor实例,并且当在其他一些库中定义类和类型时,在自己的代码中编写类的实例(行话术语是& #34;孤儿实例&#34;)几乎总是一个糟糕的主意。所以你很难写一个没有类的map函数,它有一个自己的名字:

mapUsers :: (a -> b) -> Users a -> Users b
mapUsers f (Users u1 u2 u3) = Users (f u1) (f u2) (f u3)

然而,这样做很快就会变老,用很多不需要的名称污染你的命名空间,最糟糕的是,阻止你使用其他需要Functor实例的库中的所有机制,而不是某些任意类型碰巧有一个类似fmap的函数。但是,如果您有自定义类型,则可以正常提供实例:

instance Functor Users where
    fmap f (Users u1 u2 u3) = Users (f u1) (f u2) (f u3)

(顺便说一句,我应该提一下,该语言中有一个工具,它准确地涵盖了您希望使用预先存在的类型但具有新的或不同的实例的情况:关键字newtype。但是是另一个未来的问题。)

澄清您的代码

最后,自定义名称(以及自定义字段名称,如果您使用记录语法)有自定义类型的好处,使得您使用的每个值实际上意味着更加明显,这当然是一个非常重要的收益

正如chi在评论中指出的那样,这些好处适用于任何类型的语言。然而,考虑到Haskell类型系统的强大功能,通过定义自己的类型所带来的优势比大多数其他语言都要大。

[1]:虽然如果你实际使用的是Towns a,你可能应该删除缺失的值占位符Nill ......

data Town = TownA String | TownB Int

...并在您需要Maybe Town的情况下使用Town代替Nill