Haskell中的子集代数数据类型或类型级别集

如何解决Haskell中的子集代数数据类型或类型级别集

假设您有大量类型和大量函数,每个函数都返回这些类型的“子集”。

让我们用一个小例子来使情况更明确。这是一个简单的代数数据类型:

data T = A | B | C

有两个函数fg返回一个T

f :: T
g :: T

对于当前情况,假设f仅可以返回AB并且g仅可以返回BC

我想在类型系统中对此进行编码。以下是一些可能需要这样做的原因/情况:

  • 让功能fg拥有比::T更丰富的签名
  • 强制执行fg的实现不会意外返回该实现的用户随后意外使用的禁止类型
  • 允许代码重用,例如当涉及到仅对T类型的子集起作用的辅助函数时
  • 避免样板代码(见下文)
  • 简化重构(很多!)

一种方法是拆分代数数据类型并根据需要包装单个类型:

data A = A
data B = B
data C = C

data Retf = RetfA A | RetfB B 
data Retg = RetgB B | RetgC C

f :: Retf
g :: Retg

这有效且易于理解,但是带有很多样板,可以经常解开返回类型RetfRetg

在这里,我认为多态性没有任何帮助。

因此,可能是依赖类型的情况。它实际上不是类型级别列表,而是类型级别集合,但我从未见过类型级别集合。

最后,目标是通过类型对领域知识进行编码,以便可以进行编译时检查,而不会产生过多的样板。 (当有很多类型和功能时,样板就变得很烦人。)

解决方法

定义一个辅助求和类型(用作数据类型),其中每个分支都对应于您的主要类型的版本:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DataKinds #-}
import Data.Kind
import Data.Void
import GHC.TypeLits

data Version = AllEnabled | SomeDisabled

然后定义一个类型家族,如果允许该分支,则将版本和构造函数名称(作为类型级别Symbol)映射到类型(),并将其映射到空类型{{ 1}}(如果不允许)。

Void

然后按如下所示定义您的类型:

type Enabled :: Version -> Symbol -> Type
type family Enabled v ctor where
    Enabled SomeDisabled "C" = Void
    Enabled _ _ = ()

(严格性注释可以帮助穷举性检查器。)

可以派生Typeclass实例,但可以分别为每个版本派生:

type T :: Version -> Type
data T v = A !(Enabled v "A")
         | B !(Enabled v "B")
         | C !(Enabled v "C")

这是一个使用示例:

deriving instance Show (T AllEnabled)
deriving instance Eq (T AllEnabled)
deriving instance Show (T SomeDisabled)
deriving instance Eq (T SomeDisabled)

此解决方案使模式匹配和构造更加麻烦,因为那些noC :: T SomeDisabled noC = A () main :: IO () main = print $ case noC of A _ -> "A" B _ -> "B" -- this doesn't give a warning with -Wincomplete-patterns 总是在那儿。

一个变体是每个分支具有一个类型族(如Trees that Grow),而不是两参数类型族。

,

过去,我曾尝试实现类似的目标,但是并没有取得太大的成功-我对自己的解决方案不太满意。

仍然,可以使用GADT对这种约束进行编码:

data TagA = IsA | NotA
data TagC = IsC | NotC
    
data T (ta :: TagA) (tc :: TagC) where
   A :: T 'IsA  'NotC
   B :: T 'NotA 'NotC
   C :: T 'NotA 'IsC

-- existential wrappers
data TnotC where TnotC :: T ta 'NotC -> TnotC
data TnotA where TnotA :: T 'NotA tc -> TnotA

f :: TnotC
g :: TnotA

但是,由于指数的包装/展开,这变得很无聊。消费者函数更方便,因为我们可以编写

giveMeNotAnA :: T 'NotA tc -> Int

只需要A即可。生产者函数需要使用存在性。

在具有许多构造函数的类型中,由于我们必须使用带有许多标记/参数的GADT,因此它也很不方便。也许可以使用一些聪明的typeclass机械来简化。

,

为每个值赋予其自己的类型会非常糟糕,并且不必要地进行细粒度设置。

您可能想要的只是将类型限制为 property 。例如Coq,那就是subset type

Inductive T: Type :=
     | A
     | B
     | C.

Definition Retf: Type := { x: T | x<>C }.
Definition Retg: Type := { x: T | x<>A }.

好吧,Haskell无法表达这种价值约束,但这并不能阻止您创建从概念上实现它们的类型。只需使用新类型:

newtype Retf = Retf { getRetf :: T }
mkRetf :: T -> Maybe Retf
mkRetf C = Nothing
mkRetf x = Retf x

newtype Retg = Retg { getRetg :: T }
mkRetg :: ...

然后在f的实现中,您匹配mkRetf的最终结果,如果为Nothing,则会引发错误。这样,不幸的是,实现错误使它C不会产生编译错误,但至少会产生实际错误的函数内部的运行时错误,而不是进一步的错误。

Liquid Haskell是最适合您的选择,它确实支持子集类型。我不能说太多,但是据说它还不错(并且将在新的GHC版本中得到直接支持)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res