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

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(使用前将#替换为@)

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

  • Haskell Servant 和流媒体

    我正在尝试添加一个功能到我的servant服务器将从 Amazon S3 获取文件并将其流式传输回用户 由于文件可能很大 我不想将它们下载到本地然后将它们提供给客户端 我宁愿将它们直接从 S3 流式传输到客户端 I use Amazonka
  • 什么是(函数式)反应式编程?

    Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案或互动 我读过维基百科的文章反应式编程 http en wikipedia org wiki Reac
  • 返回元组的第一个元素

    假设我创建一个将两个整数相加的函数 def addInt a Int b Int Int Int val x a b x 2 我回来了 result 2 故意为了这个问题 现在我想创建一个仅返回 x 的变量 val result addIn
  • 我需要什么类型签名才能将函数列表转换为 Haskell 代码? [复制]

    这个问题在这里已经有答案了 可能的重复 为什么 haskell 中不允许这样的函数定义 https stackoverflow com questions 6168880 why is such a function definition
  • unsafeInterleaveIO 什么时候不安全?

    与其他不安全 操作不同 文档 http hackage haskell org packages archive base latest doc html System IO Unsafe html v unsafeInterleaveIO
  • 在 Haskell 中将 Maybe Int 转换为 Int

    我正在编写以下代码 并希望找到框字符串中数字的索引 所以我用了findIndex但它返回Maybe Int值 而我只想要Int value 我怎样才能转换Maybe Int to Int值或者有什么方法可以提取Int from Maybe
  • 移动列表中特定元素的简单函数

    我是 Haskell 的新手 我正在尝试弄清楚如何创建一个函数 shift Eq a gt a gt a gt Int gt a shift x h t z 输入 一个通用列表和一个相同类型的元素 x 前提条件 元素x存在于列表中 Outp
  • “date $1”参数化查询中的 PostgreSQL 语法错误

    尝试参数化我的 SQL 查询 使用 libpq 函数PQexec参数 http www postgresql org docs current static libpq exec html 我陷入了语法错误 SELECT date 1 错误
  • 在 Haskell 中阅读 GraphML

    我正在尝试将包含单个有向图的 GraphML 文件读入 HaskellData Graph http hackage haskell org package containers 0 2 0 1 docs Data Graph html为了
  • 如何使用 swift flatMap 从数组中过滤掉选项

    我对 flatMap 有点困惑 添加到 Swift 1 2 假设我有一些可选类型的数组 例如 let possibles Int nil 1 2 3 nil nil 4 5 在 Swift 1 1 中 我会做一个过滤器 然后是一个像这样的地
  • 使用通用元组函数一次进行多次折叠

    如何编写一个接受类型函数元组的函数ai gt b gt ai并返回一个函数 该函数接受类型元素的元组ai 类型的一个元素b 并将每个元素组合成一个新的元组ai 那是签名应该是这样的 f a1 gt b gt a1 a2 gt b gt a2
  • 如何转换十进制?到小数

    可能这是一个简单的问题 但我正在尝试所有的转换方法 并且仍然有错误 你能帮我吗 小数 可为空的小数 到小数 有很多选择 decimal x decimal a decimal x works throws if x was null dec
  • 如何在Powershell中将字符串转换为UInt64?字符串到数字的转换

    考虑以下 Powershell 片段 Uint64 Memory 1GB string MemoryFromString 1GB Uint64 ConvertedMemory Convert ToUInt64 MemoryFromStrin
  • python函数返回函数[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • Haskell 中函数和函子有什么区别?只有定义吗?

    在 Haskell 中 当编写函数时 这意味着我们将某个东西 输入 映射到另一个东西 输出 我尝试 LYAH 来理解 Functor 的定义 看起来和普通 Functor 一样 函数被称为函子有什么限制吗 Functor 是否允许有 I O
  • 在运行时检查对象类型兼容性

    这是一个非常普遍的问题 但我正在做的具体事情很简单 所以我包含了代码 当我在编译时不知道两个对象的类型时 如何检查两个对象之间的类型兼容性 也就是说 我可以做if object is SomeType when SomeType是编译时已知
  • 在 C++ 中从 std::string 转换为 char *

    我正在使用 VS2012 C 我需要将 std string 转换为 char 但我在网上找不到任何材料来提供有关如何执行此操作的任何指导 任何代码示例和建议将不胜感激 Use std string bla bla char blaptr
  • Swit 中的函数式编程将数组元素分配到正确的“桶”

    我是函数式编程的新手 我的问题是我有一个主数组和固定数量的 目标 数组 我想根据每个元素的特定值将主数组中的元素分配到正确的结果数组中 我猜测一种方法是使用一个映射函数来遍历主数组元素 确定正确的 目标数组 值 基于某种逻辑 然后将元素添加
  • f# 运行总计序列

    好吧 这看起来应该很容易 但我就是不明白 如果我有一个数字序列 如何生成由运行总计组成的新序列 例如 对于序列 1 2 3 4 我想将其映射到 1 3 6 10 以适当的功能方式 Use List scan https msdn micro
  • 为什么 Haskell 中有协函子和逆变函子的区别,而范畴论却没有区别?

    这个答案是从范畴论的角度来看的 https math stackexchange com a 661989 72174包括以下语句 事实是 协函子和逆变函子之间没有真正的区别 因为每个函子只是一个协变函子 More in details a

随机推荐