gpt4 book ai didi

haskell - 如何定义只接受数字的数据类型?

转载 作者:行者123 更新时间:2023-12-03 15:18:25 25 4
gpt4 key购买 nike

我正在尝试创建一个数据类型,Point ,它的构造函数需要三个数字。最初,我写了

data Point = Point Double Double Double

但是当某些代码预期时我遇到了一些问题 Int s。

所以我把它改成
data Point a = Point a a a

但现在我想强制执行 aNum 的实例 (?) - 我只想在构造函数中接受数字。

这可能吗?如果不是,那么公认的做法是什么?我有多少次用错误的词来描述某事?

最佳答案

是的!至少如果您允许自己使用 GHC 提供的一些语言扩展。您基本上有四种选择,一种不好,一种更好,一种不如其他两种明显,一种是正确的方式™。

1.坏的

你可以写

{-# LANGUAGE DatatypeContexts #-}
data Num a => Point a = Point a a a

这将使构造函数 Point只能用 Num a 调用值。但是,它不限制 Point 的内容。值到 Num a值。这意味着如果你想在更远的地方添加两点,你仍然需要做
addPoints :: Num a => Point a -> Point a -> Point a
addPoints (Point x1 y1 z1) {- ... -}

你看到额外的 Num a宣言?这应该没有必要,因为我们知道 Point只能包含 Num a无论如何,但就是这样 DatatypeContexts工作!无论如何,您必须对需要它的每个功能进行限制。

这就是为什么,如果您启用 DatatypeContexts ,GHC 会因为使用“错误功能”而对您大喊大叫。

2. 更好

解决方案包括打开 GADT。广义代数数据类型允许您做您想做的事。然后你的声明看起来像
{-# LANGUAGE GADTs #-}
data Point a where
Point :: Num a => a -> a -> a -> Point a

使用 GADT 时,您可以通过声明类型签名来声明构造函数,就像创建类型类时一样。

对 GADT 构造函数的约束的好处是它们可以延续到创建的值——在这种情况下,这意味着你和编译器都知道唯一存在的 Point a s 的成员是 Num a s。因此,您可以写下您的 addPoint功能一样
addPoints :: Point a -> Point a -> Point a
addPoints (Point x1 y1 z1) {- ... -}

没有刺激性的额外约束。

旁注:GADT 的派生类

使用 GADT(或任何非 Haskell-98 类型)派生类需要额外的语言扩展,并且不像使用普通 ADT 那样顺利。原理是
{-# LANGUAGE StandaloneDeriving #-}
deriving instance Show (Point a)

这只会盲目地为 Show 生成代码。类,由您来确保代码类型检查。

3. 默默无闻

正如 shachaf 在对这篇文章的评论中指出的那样,您可以获得 GADT 行为的相关部分,同时保留传统 data语法通过启用 ExistentialQuantification在 GHC 中。这使得 data声明就这么简单
{-# LANGUAGE ExistentialQuantification #-}
data Point a = Num a => Point a a a

4. 正确的

然而,上述解决方案都不是社区共识。如果您询问知识渊博的人(感谢 edwardk 和 #haskell channel 中的惊人分享他们的知识),他们会告诉您 完全不限制您的类型 .他们会告诉你,你应该将你的类型定义为
data Point a = Point a a a

然后约束在 Point 上运行的任何函数s,例如将两点相加的那个:
addPoints :: Num a => Point a -> Point a -> Point a
addPoints (Point x1 y1 z1) {- ... -}

不限制您的类型的原因是,这样做时,您会严重限制您以后使用这些类型的选择,以您可能不期望的方式。例如,为您的点创建一个 Functor 实例可能很有用,如下所示:
instance Functor Point where
fmap f (Point x y z) = Point (f x) (f y) (f z)

然后你可以做一些类似近似 Point Double 的事情与 Point Int通过简单地评估
round <$> Point 3.5 9.7 1.3

这将产生
Point 4 10 1

如果您限制了 Point a,这将是不可能的。至 Num a仅 s,因为您无法为此类受约束类型定义 Functor 实例。您必须创建自己的 pointFmap函数,这将违背 Haskell 所代表的所有可重用性和模块化。

也许更有说服力的是,如果您向用户询问坐标但用户只输入其中两个,您可以将其建模为
Point (Just 4) (Just 7) Nothing

并通过映射轻松将其转换为 3D 空间中 XY 平面上的一个点
fromMaybe 0 <$> Point (Just 4) (Just 7) Nothing

这将返回
Point 4 7 0

请注意,如果您有 Num a,后一个示例将由于两个原因而不起作用。限制你的观点:
  • 您将无法为您的 Point 定义 Functor 实例,并且
  • 您根本无法存储 Maybe a在你的点坐标。

  • 如果您应用 Num a,这只是您将放弃的许多有用示例中的一个。点的约束。

    另一方面,通过限制类型你会得到什么?我能想到三个原因:
  • “我不想意外地创建一个 Point String 并试图将它作为一个数字来操作。”你将无法做到。无论如何,类型系统都会阻止你。
  • “但这是为了文档目的!我想证明一个 Point 是一个数值的集合。” ...除非它不是,例如 Point [-3, 3] [5] [2, 6]它表示轴上的替代坐标,这可能是也可能不是全部有效。
  • “我不想继续为我的所有函数添加 Num 约束!”很公平。您可以从 ghci 复制并粘贴它们。在这种情况下。在我看来,一点点键盘工作值得所有好处。
  • 关于haskell - 如何定义只接受数字的数据类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19649967/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com