为什么使用 UndecidableInstances 编译此代码,然后生成运行时无限循环?

2024-07-02

当使用编写一些代码时UndecidableInstances早些时候,我遇到了一些我觉得很奇怪的事情。我无意中创建了一些代码,当我认为不应该进行类型检查时:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}

data Foo = Foo

class ConvertFoo a b where
  convertFoo :: a -> b

instance (ConvertFoo a Foo, ConvertFoo Foo b) => ConvertFoo a b where
  convertFoo = convertFoo . (convertFoo :: a -> Foo)

evil :: Int -> String
evil = convertFoo

具体来说,convertFoo给定时进行函数类型检查any输入生产any输出,如所示evil功能。起初,我以为也许我不小心实现了unsafeCoerce,但这并不完全正确:实际上试图打电话给我convertFoo功能(通过做类似的事情evil 3,例如)只是进入无限循环。

I sort of模糊地理解正在发生的事情。我对这个问题的理解是这样的:

  • 的实例ConvertFoo我定义的作品any输入和输出,a and b,仅受转换函数必须存在的两个附加约束的限制a -> Foo and Foo -> b.
  • 不知何故,该定义能够匹配任何输入和输出类型,因此看起来调用convertFoo :: a -> Foo正在选择的定义convertFoo本身,因为无论如何它是唯一可用的。
  • Since convertFoo无限地调用自身,该函数就会进入一个永远不会终止的无限循环。
  • Since convertFoo永远不会终止,该定义的类型是底部,所以技术上没有任何类型被违反,并且程序会进行类型检查。

现在,即使上述理解是正确的,我仍然对为什么整个程序进行类型检查感到困惑。具体来说,我希望ConvertFoo a Foo and ConvertFoo Foo b假设不存在这样的实例,则约束会失败。

I do理解(至少模糊地)在选择实例时约束并不重要——仅根据实例头来选择实例,然后检查约束——所以我可以看到这些约束可能会很好地解决,因为我ConvertFoo a b例如,这是尽可能宽松的。然而,这将需要解决相同的约束集,我认为这会将类型检查器置于无限循环中,导致 GHC 要么挂起编译,要么给我一个堆栈溢出错误(后者是我见过的)前)。

但显然,类型检查器确实not循环,因为它很高兴触底并愉快地编译我的代码。为什么?在这个特定的例子中,实例上下文是如何满足的?为什么这不会给我一个类型错误或产生类型检查循环?


我完全同意这是一个很好的问题。它讲述了如何 我们对类型类的直觉与现实不同。

类型级惊喜

为了看看这里发生了什么,要提高赌注 键入签名evil:

data X

class Convert a b where
  convert :: a -> b

instance (Convert a X, Convert X b) => Convert a b where
  convert = convert . (convert :: a -> X)

evil :: a -> b
evil = convert

显然Covert a b正在选择实例,因为只有 该类的一个实例。类型检查员正在思考类似的事情 这:

  • Convert a X is true if...
    • Convert a X是真的[假设为真]
    • and Convert X X is true
      • Convert X X is true if...
        • Convert X X是真的[假设为真]
        • Convert X X是真的[假设为真]
  • Convert X b is true if...
    • Convert X X是真的[上面的说法是正确的]
    • and Convert X b是真的[假设为真]

类型检查器让我们感到惊讶。我们不期望Convert X X成为 确实如此,因为我们还没有定义类似的东西。但(Convert X X, Convert X X) => Convert X X是一种同义反复:它是 自动为 true,并且无论类中定义了什么方法,它都是 true。

这可能与我们的类型类心智模型不符。我们期望 编译器此时呆住并抱怨如何Convert X X不可能是真的,因为我们没有为它定义任何实例。我们期待 编译器站在Convert X X,寻找另一个地点 步行到哪里Convert X X是真的,并且放弃,因为有 没有其他地方是这样的。但编译器能够 递归!递归、循环并实现图灵完备。

我们为类型检查器提供了这种功能,我们是用UndecidableInstances。当文档指出它是 可以将编译器发送到循环中,很容易假设 最糟糕的是,我们假设坏循环总是无限循环。但 在这里,我们演示了一个更致命的循环,一个循环终止——除了以一种令人惊讶的方式。

(丹尼尔的评论更加明显地证明了这一点:

class Loop a where
  loop :: a

instance Loop a => Loop a where
  loop = loop

.)

不可判定性的危险

这正是这种情况UndecidableInstances允许。如果我们关闭该扩展并打开FlexibleContexts在 (一个无害的扩展,本质上只是句法),我们得到一个 对违反其中一项规定的行为发出警告帕特森 状况 https://downloads.haskell.org/~ghc/8.0.1/docs/html/users_guide/glasgow_exts.html#instance-termination-rules:

...
Constraint is no smaller than the instance head
  in the constraint: Convert a X
(Use UndecidableInstances to permit this)
In the instance declaration for ‘Convert a b’

...
Constraint is no smaller than the instance head
  in the constraint: Convert X b
(Use UndecidableInstances to permit this)
In the instance declaration for ‘Convert a b’

“不小于实例头”,尽管我们可以在心里重写它 因为“这个实例可能会被用来证明以下断言 它本身会给你带来很多痛苦、咬牙切齿和打字。” Paterson 条件共同防止实例解析中出现循环。 我们在这里的违规行为表明了为什么它们是必要的,我们可以 大概可以查阅一些论文来了解为什么它们足够了。

触底反弹

至于为什么程序在运行时无限循环:有无聊的地方 回答,哪里evil :: a -> b只能无限循环或抛出一个 例外或通常触底,因为我们信任 Haskell 类型检查器并且没有可以居住的值a -> b除了 底部。

一个更有趣的答案是,因为Convert X X是 同义反复,它的实例定义就是这个无限循环

convertXX :: X -> X
convertXX = convertXX . convertXX

我们可以类似地展开Convert A B实例定义。

convertAB :: A -> B
convertAB =
  convertXB . convertAX
  where
    convertAX = convertXX . convertAX
    convertXX = convertXX . convertXX
    convertXB = convertXB . convertXX

这种令人惊讶的行为,以及如何限制实例解析(通过 默认不带扩展名)是为了避免这些 行为,也许可以被视为哈斯克尔的一个很好的理由 类型类系统尚未得到广泛采用。尽管其 令人印象深刻的人气和力量,但也有一些奇怪的角落(无论是 它在文档或错误消息或语法中,甚至可能在 它的基本逻辑)似乎特别不适合我们人类的方式 考虑类型级抽象。

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

为什么使用 UndecidableInstances 编译此代码,然后生成运行时无限循环? 的相关文章

  • Haskell Stack 包安装错误

    user stack install dictionaries Error While constructing the build plan the following exceptions were encountered In the
  • 如何从有向无环图导出FRP?

    我目前正在研究我的下一个项目 目前处于预规划阶段 因此这个问题只是为了了解现有技术的概述 Setup 我有一个具有多个输入和输出的有向无环图 DAG 现在考虑人工神经网络 处理这种结构的常见方法是在每个 时间 步骤上处理整个网络 我相信这是
  • 对参数进行排序以利用柯里化

    我最近两次重构代码以更改参数的顺序 因为代码太多 黑客喜欢flip or x gt foo bar x 42正在发生 在设计函数签名时 哪些原则可以帮助我充分利用柯里化 对于轻松支持柯里化和部分应用的语言 有一系列令人信服的论点 最初来自
  • 使用 GHC.Generics 恢复类型定义

    昨天我尝试回答这个问题是关于数据类型的表示 https stackoverflow com questions 22715572 a serializable representation of a data type for client
  • 为什么 Kleisli 不是 Monoid 的一个实例?

    如果您希望附加两个类型 a gt m b 的函数 以便只得到一个附加两个结果的相同类型的函数 您可以使用 Kleisli 来执行此操作 instance Monad m Monoid b gt Monoid Kleisli m a b wh
  • 我可以在程序内更改堆栈大小限制吗?

    我可以通过传递配置 GHC 编译的 Haskell 程序的最大堆栈大小 RTS Kn到它 在哪里n是某个数字 有没有办法在程序内更改此设置 我想对各种函数的堆栈消耗进行基准测试 因此尝试在各种限制下运行它 捕获StackOverflow例外
  • 无法让 wxHaskell 在 Mac 上从 ghci 工作

    我正在尝试跑步一个例子 http www haskell org haskellwiki WxHaskell Quick start Hello world in wxHaskell using EnableGUI function htt
  • 优化计算 200 万以下所有素数总和的 Haskell 代码

    欧拉计划中的问题 10 我在那里看到了一些讨论 但仅限于 C 我用下面的代码来计算 print sum sieve 2 2000000 where sieve sieve x xs x sieve filter 0 mod x xs 需要很
  • Haskell 中的随机枢轴快速排序

    是否有可能在 Haskell 中实现快速排序 使用 RANDOM PIVOT 但仍然有一个简单的Ord a gt a gt a 签名 我开始了解 Monad 目前 我将 monad 解释为某种 命令模式 这对于 IO 非常有用 所以 我知道
  • 数据记录的类约束

    我有一个data type data BuildException a KillBuild JobID a Stage FailBuild JobID a Stage CancelBuild JobID a Stage StopBuild
  • Haskell 乘加运算的数学性能

    我正在用 Haskell 编写一个游戏 我当前在 UI 上的传递涉及大量几何图形的程序生成 我目前专注于识别一项特定操作的性能 C ish 伪代码 Vec4f multiplier addend Vec4f vecList for int
  • ghci 中严格求和/严格折叠导致内存爆炸

    正如中提到的为什么 sum takeWhile 以下是not炸毁记忆ghci https stackoverflow com questions 14298930 why does sum takewhile 10000000 1 use
  • 为什么这个 HasField 实例没有被解析?

    我在用着GHC 8 2 1 我有以下模块 LANGUAGE FlexibleInstances LANGUAGE MultiParamTypeClasses LANGUAGE UndecidableInstances LANGUAGE Ty
  • “引用透明”IO 调用的可重入缓存

    假设我们有一个 IO 操作 例如 lookupStuff InputType gt IO OutputType 这可能是一些简单的事情 例如 DNS 查找 或者针对时不变数据的某些 Web 服务调用 我们假设 该操作永远不会抛出任何异常和
  • 简化布尔表达式的函数

    我正在处理以下语法 我已经以 Haskell 的形式实现了data type bool tt ff bool bool var var letter letter digit 我的问题是 我想写一个函数simplify bool bool它
  • SKOS(语义网)的 Haskell 数据结构

    介绍 我正在用 Haskell 编写一个语义 Web 应用程序 使用 hsparqlhttp hackage haskell org package hsparql http hackage haskell org package hspa
  • 将列表拆分为可能的元组列表

    我需要将列表拆分为所有可能元组的列表 但我不确定如何执行此操作 例如 pairs cat dog mouse 应该导致 cat dog cat mouse dog cat dog mouse mouse cat mouse dog 我能够形
  • “约束不小于实例头”是什么意思以及如何解决

    我想写一些类似的东西 LANGUAGE FlexibleContexts FlexibleInstances import Data ByteString Char8 ByteString pack import Data Foldable
  • Haskell 约束不小于实例头

    有些戒指可以配备标准功能 class Ring C a gt EuclideanDomain a where norm a gt Integer 使用此功能 可以通过明显的方式订购戒指 compare x y compare norm x
  • 检查范围是否包含 Scala 中的值的通用方法

    我想编写一个通用类来保存范围的端点 但通用版本会出现编译错误 value gt is not a member of type parameter A final case class MinMax A lt Comparable A mi

随机推荐

  • Angular 8滚动到片段,不会将片段带到页面顶部

    我有一个链接 通过单击该链接 我想滚动到页面底部的片段 当我单击链接时 片段正在工作 但不会将其带到页面顶部 我尝试使用带有 id 的 div 和节来创建片段 但是 它不会将 div 或部分带到页面顶部 我的应用程序路由模块中的代码是 im
  • 如何通过 eclipse 在 JUnit 测试中使用 persistence.xml 和 hibernate.cfg.xml?

    在来这里问这个问题之前 我已经寻找了很长一段时间的答案 我的问题非常简单 使用 JPA Entity Manager API 时无法运行任何 JUnit 测试 看来我的测试没有阅读persistence xml file 当我打电话时new
  • 有 PHP 函数可以解决这个问题吗? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 对不起 我希望这并不奇怪或什么 我该如何修复这个编码 您遇到的情况是数据以一种编码写入并解释为另一种编码的结果 您需要确保请求输入的格
  • 更改 Android 中的配对对话框外观

    我们有一个与 BLE 设备 我们也生产 配对的 Android 应用程序 但默认的 Android 配对对话框存在一些问题 问题是 我们的设备不需要访问联系人或通话记录 是否可以删除该选项 无论是否选中该框 配对和我们的功能都将起作用 但我
  • 使用同步ajax调用有什么缺点?

    这个问题肯定适用于 jQuery 但在本例中我指的是 Prototype 在原型文档中它说 由于同步使用相当 令人不安 而且通常品味不好 你 应该避免改变这一点 严重地 我不确定使用同步 ajax 调用有什么缺点 似乎有很多情况您必须等待调
  • 如何从javascript中的base 64字符串获取图像文件大小? [复制]

    这个问题在这里已经有答案了 我有图像的 Base64 数据 data image png base64 iVBORw0KGgoAAAANSUhEUgAAAOcAAABnCAYAAAD7RFX4AAAACXBIWXMAAAsTAAALEwEA
  • 为什么追加会覆盖列表?

    我正在尝试 hackerrank 的一些问题并遇到了这个问题https www hackerrank com challenges list com经理 问题 https www hackerrank com challenges list
  • 如何向 .gitignore 添加一些内容以使匹配不递归?

    如何添加一些东西到 gitignore这样匹配就不是递归的 例如 我想忽略目录foo和文件bar txt在当前目录中 但不在子目录中存在 我已经为我尝试过这个 gitignore file foo bar txt 但不幸的是 git 递归地
  • 使用 itertools.product 并想要播种一个值

    所以我写了一个小脚本来从网站下载图片 它通过 7 个字母字符值 其中第一个字符始终是数字 问题是 如果我想停止脚本并再次启动它 我必须从头开始 我可以用我得到的最后一个值以某种方式播种 itertools product 吗 这样我就不必再
  • 寻找 MKOverlayPathRenderer 示例

    我正在尝试弄清楚如何使用新的MKOverlayPathRenderer class 在我之前使用的应用程序中MKOverlayPathView使用 iOS 6 SDK 构建时 但不幸的是 它似乎不适用于 iOS 7 SDK 所以我试图将我的
  • 具有 BETWEEN 时间戳的 SQL 查询出现意外结果

    我创建了一个小测试应用程序来追踪我在 Heroku 上使用 Postgres 时遇到的问题 http snippi com s xd511rf http snippi com s xd511rf 正如你在队列中看到的49 我想检索所有创建的
  • 硒元素不相互作用

    我开始使用selenium node js 到目前为止一切正常 突然相同的脚本抛出错误 未处理的承诺拒绝警告元素不可交互 我尝试设置等待 直到什么也没有
  • 查找条目多于 R 中某个值的行总和

    我有以下矩阵 m structure 1 20 Dim 4 5 m 1 2 3 4 5 1 1 5 9 13 17 2 2 6 10 14 18 3 3 7 11 15 19 4 4 8 12 16 20 gt 我想找到每行中条目值大于 5
  • 如何向 XML DOM 对象添加命名空间前缀?

    我正在尝试使用特定的命名空间构建 XML 文档 我尝试生成的最终文档应该如下所示
  • 如何将异步函数放入 Rust 的映射中?

    在编写异步路由器时 我无法处理异步函数hyper 这段代码 use std collections HashMap use std future Future type BoxedResult
  • Python 中的一维马哈拉诺比斯距离

    我一直在努力validate我的计算代码马哈拉诺比斯距离写在Python 并仔细检查以比较 OpenCV 中的结果 我的数据点均为 1 维 5 行 x 1 列 In OpenCV C 我成功计算了马哈拉诺比斯距离方面数据点的尺寸为上述尺寸
  • 在内存对象缓存中开发

    我正在开发一个基于网络的医疗应用程序 需要创建一个小型内存对象缓存 这是我的用例 我们需要显示需要某些东西 血液 肾脏等 的人提交的请求列表 并且它不会是一个巨大的列表 因为在某一天对血液或其他任何东西的请求将是有限的 请注意 我们不想使用
  • Cocoa:阻止字段编辑器按空格键自动完成

    我有一个自定义视图有几个NSTextField我想为其提供自定义自动完成功能的控件 并且我已经使用NSTextFieldDelegate协议 自动完成是全名或地名 具体取决于正在编辑的文本字段 问题是自动完成几乎总是包含空格字符 因此如果用
  • 当找到路由/url 但未找到其背后的资源时返回什么?

    当路由customer 1存在但customer搜索背后的资源 实体不存在时 我应该返回 404 吗 我的意思是路线存在 或者我应该返回一个 204 无内容 因为我找不到客户 结果为空 微软样本 public IHttpActionResu
  • 为什么使用 UndecidableInstances 编译此代码,然后生成运行时无限循环?

    当使用编写一些代码时UndecidableInstances早些时候 我遇到了一些我觉得很奇怪的事情 我无意中创建了一些代码 当我认为不应该进行类型检查时 LANGUAGE FlexibleInstances LANGUAGE MultiP