为什么类型系统拒绝我看似有效的程序?

2024-04-21

注意这个程序:

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

data A = A deriving Show
data B = B deriving Show
data C = C deriving Show
data D = DA A | DB B | DC C deriving Show

instance Convert A A where convert A = A
instance Convert A B where convert A = B
instance Convert A C where convert A = C
instance Convert B A where convert B = A
instance Convert B B where convert B = B
instance Convert B C where convert B = C
-- There is no way to convert from C to A
instance Convert C B where convert C = B
instance Convert C C where convert C = C

get (DA x) = convert x
get (DB x) = convert x
get (DC x) = convert x

main = do
    print (get (DC C) :: B) -- Up to this line, code compiles fine.
    print (get (DB B) :: A) -- Add this line and it doesn't, regardless of having a way to convert from B to A!

有实例可以转换C to B和来自B to A。然而,GHC 对前者进行类型检查,但对后者失败。经过检查,似乎它无法推断出足够通用的类型get:

get :: (Convert A b, Convert B b, Convert C b) => D -> b

我想表达的是:get :: (Convert a_contained_by_D b) => D -> b,这似乎是不可能的。有什么方法可以实现和编译一个函数来完成我的任务get尝试在不更改其余设置的情况下做什么?


如果你的程序really对你来说似乎有效,那么你就可以写出以下类型get这可以在 Haskell 中完成你想要的工作,而不是在 handwave 中。让我帮助你改进你的手波并揭开你要求棒上月亮的原因。

我想表达的是:get :: (Convert a_contained_by_D b) => D -> b,这似乎是不可能的。

如前所述,这并不像您需要的那么精确。事实上,这就是 Haskell 现在给你的,因为

get :: (Convert A b, Convert B b, Convert C b) => D -> b

any a可以包含在D需要一次一个,才能转换为该b。这就是为什么你会得到经典的系统管理逻辑:不D被允许获得,除非他们都可以b.

问题是您需要知道状态而不是可能包含在any old D,而是特定中包含的类型D您收到的输入。正确的?你要

print (get (DB B) :: A)  -- this to work
print (get (DC C) :: A)  -- this to fail

but DB B and DC C只是两个不同的元素D,就 Haskell 类型系统而言,在每种类型中一切不同都是一样的。如果你想区分以下元素D,那么你需要一个D- 下垂型。这是我用手波写的方式。

DInner :: D -> *
DInner (DA a) = A
DInner (DB b) = B
DInner (DC c) = C

get :: forall x. pi (d :: D) -> (Convert (DInner d) x) => x
get (DA x) = convert x
get (DB x) = convert x
get (DC x) = convert x

where pi是在运行时传递的数据的绑定形式(与forall)但取决于哪些类型可能取决于(与->)。现在的约束不是任意的D但非常d :: D在你的手中,约束可以通过检查它来准确计算出所需的内容DInner.

你无法说任何话可以让它消失,但我的pi.

可悲的是,同时pi正在从天而降,还没有落地。尽管如此,与月球不同的是,它可以用棍子到达。毫无疑问你会抱怨我正在改变设置,但实际上我只是将你的程序从大约 2017 年的 Haskell 翻译成 2015 年的 Haskell。你会get有一天,它回来了,和我挥手时的那种类型一样。

你无话可说,但你可以sing.

步骤 1. 开机DataKinds and KindSignatures并为您的类型构建单例(或者让 Richard Eisenberg 为您做)。

data A = A deriving Show
data Aey :: A -> * where  -- think of "-ey" as an adjectival suffix
  Aey :: Aey 'A           -- as in "tomatoey"

data B = B deriving Show
data Bey :: B -> * where
  Bey :: Bey 'B

data C = C deriving Show
data Cey :: C -> * where
  Cey :: Cey 'C

data D = DA A | DB B | DC C deriving Show
data Dey :: D -> * where
  DAey :: Aey a -> Dey (DA a)
  DBey :: Bey b -> Dey (DB b)
  DCey :: Cey c -> Dey (DC c)

这个想法是(i)数据类型成为种类,(ii)单例表征具有运行时表示的类型级数据。所以输入级别DA a在运行时存在a确实如此,等等

第 2 步:猜猜谁会来DInner。打开TypeFamilies.

type family DInner (d :: D) :: * where
  DInner (DA a) = A
  DInner (DB b) = B
  DInner (DC c) = C

步骤 3. 给你一些RankNTypes,现在你可以写

get :: forall x. forall d. Dey d -> (Convert (DInner d) x) => x
--               ^^^^^^^^^^^^^^^^^^
-- this is a plausible fake of pi (d :: D) ->

步骤 4. 尝试写get搞砸了。我们必须匹配类型级别的运行时证据d是有代表性的。我们需要它来获得类型级别d专门从事计算DInner。如果我们有适当的pi,我们可以匹配D具有双重职责的价值,但就目前而言,匹配Dey d反而。

get (DAey x) = convert x   -- have x :: Aey a, need x :: A
get (DBey x) = convert x   -- and so on
get (DCey x) = convert x   -- and so forth

令人抓狂的是,我们的xes 现在是单例,其中,convert,我们需要底层数据。我们需要更多的单例设备。

步骤 5. 引入并实例化单例类,以“降级”类型级别值(只要我们知道它们的运行时代表)。再次,理查德·艾森伯格的singletons库可以通过 Template-Haskell 来摆脱这个样板,但让我们看看发生了什么

class Sing (s :: k -> *) where   -- s is the singleton family for some k
  type Sung s :: *               -- Sung s is the type-level version of k
  sung :: s x -> Sung s          -- sung is the demotion function

instance Sing Aey where
  type Sung Aey = A
  sung Aey = A

instance Sing Bey where
  type Sung Bey = B
  sung Bey = B

instance Sing Cey where
  type Sung Cey = C
  sung Cey = C

instance Sing Dey where
  type Sung Dey = D
  sung (DAey aey) = DA (sung aey)
  sung (DBey bey) = DB (sung bey)
  sung (DCey cey) = DC (sung cey)

步骤 6. 开始吧。

get :: forall x. forall d. Dey d -> (Convert (DInner d) x) => x
get (DAey x) = convert (sung x)
get (DBey x) = convert (sung x)
get (DCey x) = convert (sung x)

请放心,当我们有适当的pi, those DAeys 将是实际的DA和那些xs 将不再需要sung。我的手波类型为get将是 Haskell,你的代码是get会没事的。但与此同时

main = do
  print (get (DCey Cey) :: B)
  print (get (DBey Bey) :: A)

类型检查就好了。也就是说,你的程序(加上DInner以及正确的类型get) 看起来像是有效的 Dependent Haskell,而且我们已经快到了。

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

为什么类型系统拒绝我看似有效的程序? 的相关文章

  • MPI_Type_Create_Hindexed_Block 生成派生数据类型的错误范围

    使用Fortran 我尝试为动态分配的结构构建派生数据类型 但它得到了新类型的错误范围 代码如下 PROGRAM MAIN IMPLICIT NONE INCLUDE mpif h INTEGER I INTEGER MYID NUMPRO
  • 由于垃圾收集,Haskell 程序中会出现多长时间的暂停?

    关于我的另一个问题Haskell 集合可以保证每个操作的最坏情况范围 https stackoverflow com q 12393104 1333025 我很好奇 垃圾收集会导致多长时间的暂停 Haskell 是否使用某种增量垃圾收集 以
  • 相当于 Java 中 C++ 的 std::bind 吗?

    有没有一种方法可以像 C 中的 std bind 一样将 Java 中的参数绑定到函数指针 Java 中类似的东西会是什么 void PrintStringInt const char s int n std cout lt lt s lt
  • 在列表中查找元素及其索引

    我需要让列表的两个元素都满足谓词and这些元素的索引 我可以通过以下方式实现这一点 import Data List findIndices list Int list 3 2 4 1 9 indices findIndices gt 2
  • 哈希密码字段使用什么数据类型以及长度?

    我不确定密码哈希是如何工作的 稍后将实现 但现在需要创建数据库模式 我正在考虑将密码限制为 4 20 个字符 但据我了解 加密后哈希字符串的长度将有所不同 那么 如何将这些密码存储在数据库中呢 更新 仅使用哈希函数不足以存储密码 你应该阅读
  • Haskell数据类型转换问题

    我目前正在学习 Haskell 并且一直在编写一些非常简单的程序来练习 我的程序之一是 import System IO main do putStrLn Give me year y lt getLine let res show cal
  • 我可以在 Java 8 中使用 Clojure 函数作为 Lambda 函数吗?

    我在 Clojure 中使用了许多库来生成符合 Clojure lang IFN https github com clojure clojure blob master src jvm clojure lang IFn java 界面 它
  • mysql_query 保留返回时在表中创建的数据类型?

    我在mysql中有一个表 CREATE TABLE user id INT name VARCHAR 250 我查询表 result mysql query SELECT id name FROM user 我收集结果 while row
  • 枚举类型的 JAXB 元素

    所以我知道如何创建枚举类型 但是当我为其设置元素类型时 元素字段将只是字符串类型 而不是枚举类型 如何在我的模式中创建枚举并让 JAXB 将其生成为 java 枚举类型 这就是我创建枚举类型和元素的方式
  • 隐秘模板模板参数错误

    我正在尝试创建一个从 a 获取密钥的函数std map or an std unordered map 我可以使用简单的重载 但首先我想知道这段代码有什么问题 template
  • 为什么 GHC 在这里推断出单态类型,即使禁用了单态限制?

    这是由解析 f f pure 的类型 https stackoverflow com questions 55388119 resolving the type of f f pure 55388309 noredirect 1 comme
  • Haskell 中的“修复”是什么?为什么“修复错误”会打印无限字符串?为什么“拿 10 美元修复错误”也有同样的作用?

    长话短说 我在看西蒙 佩顿 琼斯的演讲 https www youtube com watch v re96UgMk6GQ 并且当时21 41 https youtu be re96UgMk6GQ t 1301他引用了一句话 我正在解决一个
  • Cabal:使用源代码构建目录

    我有一个src目录 在这个目录中我有Main hs文件和Test目录 在里面Test我有的目录Test hs模块 我需要用 cabal 来编译它 在我的阴谋集团文件中 我有 Executable main hs or lhs file co
  • Haskell/Idris 中的开放类型级别证明

    在 Idris Haskell 中 可以通过注释类型并使用 GADT 构造函数 例如使用 Vect 来证明数据的属性 但这需要将属性硬编码到类型中 例如 Vect 必须是与 List 不同的类型 是否有可能拥有具有开放属性集的类型 例如同时
  • OCaml - 什么数据类型是 some 和 none?

    如果我正在使用Some and None列表中的组合 列表的数据类型是什么 是不是总是 a 或者有某种类型Some None let listVar type here list Some 4 Some 3 None Some 2 如果我把
  • 在 Python 中,部分函数应用(柯里化)与显式函数定义

    在 Python 中 以下方式是否被认为是更好的风格 根据更一般的 可能是内部使用的功能显式定义有用的功能 或者 使用偏函数应用来显式描述函数柯里化 我将通过一个人为的例子来解释我的问题 假设编写一个函数 sort by scoring 它
  • 承诺的反面是什么?

    承诺代表将来可能可用 或无法实现 的值 我正在寻找的是一种数据类型 它表示将来可能变得不可用的可用值 可能是由于错误 Promise a b TransitionFromTo
  • 如何将屏幕截图转换为二进制?

    我正在开发一个 Xamarin Forms 项目 在该项目中我想将我的屏幕截图 PNG 转换为二进制并将其上传到服务器 现在服务器部分需要一个API 我将由已经完成它的人交给我 我只需要实现它 在我完成这个任务之后 到目前为止 我已经成功地
  • C# 中值类型和引用类型有什么区别? [复制]

    这个问题在这里已经有答案了 我知道一些差异 值类型存储在堆栈上 而引用类型存储在托管堆上 值类型变量直接包含它们的值 而引用变量仅包含对托管堆上创建的对象位置的引用 我错过了任何其他区别吗 如果是的话 它们是什么 请阅读 堆栈是一个实现细节
  • seq在haskell中代表什么

    我是 Haskell 的新手 刚刚进入惰性世界编程 我读到seq函数非常特殊 因为它强制使用严格的评估 以便在某些情况下更加有效 但我就是找不到什么seq代表字面意思 也许严格评估Q 它应该提醒您 顺序 或 顺序 因为它允许程序员指定其参数

随机推荐