Haskell 中自动函数约束推导的类型约束

2024-04-19

出于教育目的,我在 Haskell 中摆弄树木。我有Tree a像这样定义的类型

data Tree a = EmptyTree | Node a (Tree a) (Tree a)

以及许多共享基本约束的函数 -Ord a- 所以他们有这样的类型

treeInsert :: Ord a => a -> Tree a -> Tree a
treeMake :: Ord a => [a] -> Tree a

等等。我还可以定义Tree a像这样

data Ord a => Tree a = EmptyTree | Node a (Tree a) (Tree a)

但我无法简化我的功能并省略额外的Ord a如下:

treeInsert :: a -> Tree a -> Tree a
treeMake :: [a] -> Tree a

为什么 Haskell (运行-XDatatypeContexts)不会自动推导出这个约束?对我来说,这似乎是很明显的。为什么我错了?

这是一些示例源代码

data (Eq a, Ord a) => Tree a = EmptyTree | Node a (Tree a) (Tree a)

treeInsert :: a -> Tree a -> Tree a
treeInsert a EmptyTree = Node a EmptyTree EmptyTree
treeInsert a node@(Node v left right)
 | a == v = node
 | a > v = Node v left (treeInsert a right)
 | a < v = Node v (treeInsert a left) right

mkTree :: [a] -> Tree a
mkTree [] = EmptyTree
mkTree (x:xs) = treeInsert x (mkTree xs)

我得到这个

Tree.hs:5:26:
    No instance for (Ord a)
      arising from a use of `Node'
    In the expression: Node a EmptyTree EmptyTree
    In an equation for `treeInsert':
        treeInsert a EmptyTree = Node a EmptyTree EmptyTree
Failed, modules loaded: none. 

这是关于数据声明上下文的一个众所周知的问题。如果你定义data Ord a => Tree a = ...它所做的只是强制任何提到的函数Tree a拥有一个Ord a语境。这不会节省您任何输入,但从好的方面来说,明确的上下文清楚地说明了需要什么。

GADT 来救援!

解决方法是使用广义抽象数据类型 https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#generalised-algebraic-data-types-gadts (GADTs).

{-# Language GADTs, GADTSyntax #-}

我们可以通过提供显式类型签名将上下文直接放在构造函数上:

data Tree a where
   EmptyTree :: (Ord a,Eq a) => Tree a
   Node :: (Ord a,Eq a) => a -> Tree a -> Tree a -> Tree a

然后每当我们进行模式匹配时Node a left right我们得到一个隐含的(Ord a,Eq a)上下文,就像你想要的那样,所以我们可以开始定义treeInsert像这样:

treeInsert :: a -> Tree a -> Tree a
treeInsert a EmptyTree = Node a EmptyTree EmptyTree
treeInsert a (Node top left right) 
          | a < top   = Node top (treeInsert a left) right
          | otherwise = Node top left (treeInsert a right) 

派生东西

如果你只是添加deriving Show在那里,你会得到一个错误:

Can't make a derived instance of `Show (Tree a)':
  Constructor `EmptyTree' must have a Haskell-98 type
  Constructor `Node' must have a Haskell-98 type
  Possible fix: use a standalone deriving declaration instead
In the data type declaration for `Tree'

这很痛苦,但就像它说的,如果我们添加StandaloneDeriving扩大 ({-# Language GADTs, GADTSyntax, StandaloneDeriving #-})然后我们可以做类似的事情

deriving instance Show a => Show (Tree a)
deriving instance Eq (Tree a) -- wow

一切顺利。哇是因为隐含的Eq a上下文意味着我们不需要明确的Eq a在实例上。

上下文仅来自构造函数

请记住,您只能通过使用其中一个构造函数来获取隐式上下文。 (请记住,上下文是在此处定义的。)

这实际上就是为什么我们需要上下文EmptyTree构造函数。如果我们只是把EmptyTree::Tree a, 线

treeInsert a EmptyTree = Node a EmptyTree EmptyTree

不会有(Ord a,Eq a)等式左侧的上下文,因此右侧的实例将丢失,而右侧则需要它们Node构造函数。这将是一个错误,因此在这种情况下保持上下文一致会很有帮助。

这也意味着你不能拥有

treeMake :: [a] -> Tree a

treeMake xs = foldr treeInsert EmptyTree xs

你会得到一个no instance for (Ord a)错误,因为左侧没有构造函数可以为您提供(Ord a,Eq a) context.

这意味着你还需要

treeMake :: Ord a => [a] -> Tree a

抱歉,这次没有办法解决这个问题,但从好的方面来说,这很可能是您想要编写的唯一一个不带树参数的树函数。Most树函数将在定义的左侧获取一棵树并对其执行某些操作,因此大多数时候您都会拥有隐式上下文。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Haskell 中自动函数约束推导的类型约束 的相关文章

随机推荐

  • 比使用流保存增强随机生成器状态更快的替代方案

    我需要能够保存 加载这个增强随机生成器的状态 boost variate generator
  • 致命:读取错误:连接被对等方重置

    有人可以帮我摆脱以下问题 vijay13 ubuntu git clone git anongit kde org plasma mediacenter Cloning into plasma mediacenter fatal read
  • 调试 GWT 应用程序时 Eclipse 挂起

    我们正在使用 JAVA GWT P 框架 版本 2 4 开发 Web 应用程序 我们使用 Eclipse 版本 3 7 Indigo 作为开发 GUI 当我们调试应用程序时 Eclipse 通常会挂起 令人惊讶的是 这是一种随机行为 而且这
  • 为什么相邻同级选择器不起作用? [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 根据相邻同级选择器的定义 以下代码应该可以工作 然而事实并非如此 我似乎没有发现任何错误 p p p This is the sibling
  • Mysql 查询日期 >= 90 天

    我想在数据库中查询日期等于或大于 90 天的记录 这是我到目前为止所拥有的 format Y m j G i s date date format 90 days from today date format strtotime 90 da
  • 使用反射设置枚举

    如何使用反射设置枚举 我的班级有枚举 public enum LevelEnum NONE CRF SRS HLD CDD CRS 在运行时我想将该枚举设置为 CDD 例如 我该怎么做 尝试使用类枚举 LevelEnum s LevelEn
  • Python 中的基本转换器

    我正在编写一个程序 用于在 Python 中将任何基数转换为基数 10 该程序的代码如下所示 print Enter the number you want to convert to base 10 number input length
  • 如何将 CLOB 数据从一个数据库传输到另一个具有 DBLinks 的远程 ORACLE 数据库

    问题是 如何将 CLOB 数据从一个源数据库传输到另一个具有 DBLink 的 Oracle 数据库 Oracle无法使用DBLinks传输CLOB数据 那么除了 将Oracle中的字段扩展为Varchar2 32 767个字符 Oracl
  • Javascript - 在 ES5 中扩展 ES6 类

    我正在使用以下代码作为滑块Siema https pawelgrzybek github io siema https codepen io pawelgrzybek pen boQQWy https codepen io pawelgrz
  • 命令提示符中的代码在批处理文件中不起作用

    当我在命令提示符中执行下面的代码时 它会执行我想要的操作 但当我将其放入 bat 文件并尝试执行它时 它不会执行我想要的操作 for f a in dir b csv do for f tokens b in a do echo b a g
  • jQuery - 如何将多个节点附加到容器

    我需要将多个节点附加到容器中 我不想在每次迭代中执行缓慢的 DOM 追加 而是想将文档片段中的节点排队 对其他想法开放 并一次将它们全部追加 这是我的代码 var fragment document createDocumentFragme
  • macOS Sierra 安装 PHP 扩展 intl

    我正在尝试让 magento 2 x 在我的机器上运行 我在用xampp 5 6使用相同的 php 版本并运行虚拟 apache 服务器 As seen in this screenshot The PHP Extension intl i
  • 在全局范围内声明命名空间错误

    我有 3 个文件 Test h Test cpp 和 main cpp Test h ifndef Test H define Test H namespace v int g 9 class namespce public namespc
  • 响应式表格,智能方式

    我有一个包含数据的表 表格数据 它看起来像这样 See 这把小提琴 http jsfiddle net MrLister c54RN 现在我想要的是 当它显示在较窄的屏幕上时 表格看起来像这样 这样你就不会得到水平滚动条并且它保持相同的视觉
  • 尝试使用锐利的 Node.js 调整流图像的大小

    我正在尝试使用锐利功能调整从用户到服务器的输入流图像的宽度和高度 但图像没有任何反应 它保持原来的大小 我应该如何使用锐化功能 以便我可以使图像变小或变大 请帮我 这就是我的代码的样子 use strict const builder re
  • 比较堆栈中的两个值? [复制]

    这个问题在这里已经有答案了 我卡住了 在我的汇编代码中 我想比较两个值 堆 x86 语法 AT T cmpl 4 ebp 4 ebp 错误 cmp 的内存引用太多 我认为不可能根据乘数和 ebp 来比较两个值 有什么建议 您可以使用 CMP
  • 如何将空白复选框作为 false 传递给参数

    我有一个更新用户表单的表单 其中几个元素是复选框 我希望 true 传递给参数 如果选中 这是工作 false 传递给参数 如果未选中 不工作 未经检查的项目甚至没有发送到参数 如何使未检查的项目作为错误通过 Form h4 Please
  • Junit 异常测试用例 [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 public class TipException extends Exception private final Object mSour
  • Python ARIMA模型,预测值发生偏移

    我是 Python ARIMA 实现的新手 我有几个月 15 分钟一次的数据 我尝试遵循 Box Jenkins 方法来拟合时间序列模型 我在最后遇到了一个问题 这ACF PACF图 https i stack imgur com weNJ
  • Haskell 中自动函数约束推导的类型约束

    出于教育目的 我在 Haskell 中摆弄树木 我有Tree a像这样定义的类型 data Tree a EmptyTree Node a Tree a Tree a 以及许多共享基本约束的函数 Ord a 所以他们有这样的类型 treeI