不同种类的ReaderT?

2024-04-25

冒着成为一个XY问题 https://en.wikipedia.org/wiki/XY_problem,是否有可能有一个ReaderT与不同的环境?我正在尝试类似...

type AppM (perms :: [*]) = ReaderT (perms :: [*]) IO

...但是编译器抱怨...

Expected a type, but ‘(perms :: [*])’ has kind ‘[*]’

...大概是因为ReaderT被定义为...

newtype ReaderT r (m :: k -> *) (a :: k) = ReaderT {runReaderT :: r -> m a}

...在哪里r是善良的*

我正在尝试在类型级别跟踪权限/角色,我的最终目标是编写诸如...之类的函数

ensurePermission :: (p :: Permission) -> AppM (p :. ps) ()

...每次打电话的地方ensurePermission在 monad 的权限列表中追加/添加新的权限(在类型级别)。

Edit

我尝试了以下操作,似乎可以编译,但我不确定发生了什么。从概念上讲不是perms依然善良[*]。为什么这个代码片段能够被编译器接受,而原始代码却不能被编译器接受呢?

data HList (l :: [*]) where
  HNil :: HList '[]
  HCons :: e -> HList l -> HList (e ': l)

type AppM (perms :: [*]) = ReaderT (HList perms) IO

Edit #2

我尝试改进我的代码片段以进一步匹配我的最终目标,但我再次遇到了一个不同的“种类”问题:

编译器不接受以下代码:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}

data Permission = PermissionA
                | PermissionB

$(genSingletons [''Permission])

data PList (perms :: [Permission]) where
  PNil :: PList '[]
  PCons :: p -> PList perms -> PList (p ': perms)

--     • Expected kind ‘[Permission]’, but ‘p : perms’ has kind ‘[*]’
--     • In the first argument of ‘PList’, namely ‘(p : perms)’
--       In the type ‘PList (p : perms)’
--       In the definition of data constructor ‘PCons’
--    |
-- 26 |   PCons :: p -> PList perms -> PList (p ': perms)
--    |                                       ^^^^^^^^^^

它也不接受以下变体......

data PList (perms :: [Permission]) where
  PNil :: PList '[]
  PCons :: (p :: Permission) -> PList perms -> PList (p ': perms)


--     • Expected a type, but ‘(p :: Permission)’ has kind ‘Permission’
--     • In the type ‘(p :: Permission)’
--       In the definition of data constructor ‘PCons’
--       In the data declaration for ‘PList’
--    |
-- 26 |   PCons :: (p :: Permission) -> PList perms -> PList (p ': perms)
--    |            ^^^^^^^^^^^^^^^^^

是的,我认为我们这里有一个 XY 问题,所以让我们退后一步。

A Reader是一个携带 a 的 monadvalue可以方便地阅读。你没有值——你有一个想要在类型级别强制执行的权限列表——所以我认为你不需要或不想要一个阅读器,或者一个异构列表,或者其他类似的东西。

相反,给定布尔权限列表:

data Permission = PermissionA | PermissionB deriving (Show)

您想要定义一个在类型级别参数化的 monad 及其授予的权限列表。围绕您的基础设施的新型包装器IO单子会做:

{-# LANGUAGE DataKinds, KindSignatures, GeneralizedNewtypeDeriving #-}
newtype M (ps :: [Permission]) a = M (IO a) deriving (Functor, Applicative, Monad)

您还需要一个类型函数(又称类型族)来确定权限是否在权限列表中:

{-# LANGUAGE TypeFamilies, TypeOperators #-}
type family Allowed (p :: Permission) ps where
  Allowed p '[] = False
  Allowed p (p:ps) = True
  Allowed p (q:ps) = Allowed p ps

现在,如果您想编写需要某些权限的函数,您可以编写如下内容:

deleteA :: (Allowed PermissionA ps ~ True) => M ps ()
deleteA = M $ print "Deleted A"

readB :: (Allowed PermissionB ps ~ True) => M ps ()
readB = M $ print "Read B"

copyBtoA :: ( Allowed PermissionA ps ~ True
            , Allowed PermissionB ps ~ True) => M ps ()
copyBtoA = M $ print "Copied B to A"

运行一个Maction 中,我们引入了一个无需权限即可运行的函数:

-- runM with no permissions
runM :: M '[] a -> IO a
runM (M act) = act

请注意,如果您尝试runM readB,你会得到一个类型错误(无法匹配False with True-- 不是最大的错误消息,但是......)。

为了授予权限,我们引入函数:

-- grant permissions
grantA :: M (PermissionA:ps) a -> M ps a
grantA (M act) = M act
grantB :: M (PermissionB:ps) a -> M ps a
grantB (M act) = M act

这些函数本质上是术语级别的恒等函数——它们只是展开并重新包装M构造函数。但是,它们在类型级别的操作是为其输入参数添加权限。这意味着:

runM $ grantB $ readB

现在进行类型检查。所以这样做:

runM $ grantA . grantB $ readB
runM $ grantB . grantA $ readB
runM $ grantB . grantA . grantB $ readB
etc.

然后你可以编写如下程序:

program :: IO ()
program = runM $ do
  grantA $ do
    deleteA
    grantB $ do
      readB
      copyBtoA

同时拒绝以下计划:

program1 :: IO ()
program1 = runM $ do
  grantA $ do
    deleteA
    grantB $ do
      readB
    copyBtoA    -- error, needs PermissionB

这个基础设施可能有点丑陋,但它应该是基于类型的、完全编译时权限检查所需的全部。

也许尝试一下这个版本,看看它是否满足您的需求。完整代码是:

{-# LANGUAGE DataKinds, KindSignatures, GeneralizedNewtypeDeriving,
             TypeFamilies, TypeOperators #-}

data Permission = PermissionA | PermissionB deriving (Show)

newtype M (ps :: [Permission]) a = M (IO a) deriving (Functor, Applicative, Monad)

type family Allowed (p :: Permission) ps where
  Allowed p '[] = False
  Allowed p (p:ps) = True
  Allowed p (q:ps) = Allowed p ps

-- runM with no permissions
runM :: M '[] a -> IO a
runM (M act) = act

-- grant permissions
grantA :: M (PermissionA:ps) a -> M ps a
grantA (M act) = M act
grantB :: M (PermissionB:ps) a -> M ps a
grantB (M act) = M act

deleteA :: (Allowed PermissionA ps ~ True) => M ps ()
deleteA = M $ print "Deleted A"

readB :: (Allowed PermissionB ps ~ True) => M ps ()
readB = M $ print "Read B"

copyBtoA :: ( Allowed PermissionA ps ~ True
            , Allowed PermissionB ps ~ True) => M ps ()
copyBtoA = M $ print "Copied B to A"

program :: IO ()
program = runM $ do
  grantA $ do
    deleteA
    grantB $ do
      readB
      copyBtoA

基于@dfeuer 评论的两个附加注释。首先,它提醒我grantA and grantB同样可以使用“安全”来编写coerce函数来自Data.Coerce如下。这个版本和上面的版本生成的代码没有区别,所以这只是个人喜好的问题:

import Data.Coerce

-- grant permissions
grantA :: M (PermissionA:ps) a -> M ps a
grantA = coerce
grantB :: M (PermissionB:ps) a -> M ps a
grantB = coerce

其次,@dfeuer 所说的是,用于控制权限的可信代码基础和依赖类型系统来强制执行权限系统的“其余”代码之间没有明确的区别。例如,模式匹配M构造函数本质上是危险的,因为你可以提取一个IO a从一个权限上下文中并在另一个权限上下文中重建它。 (这基本上就是grantA and grantB正在做无条件提升权限。)如果您在受信任的代码库之外“意外”执行此操作,您最终可能会绕过权限系统。在许多应用中,这并不是什么大问题。

但是,如果您试图证明系统安全,您可能需要一个小型的可信代码库来处理危险的情况。M构造函数并仅导出“安全”API,通过类型系统确保安全性。在这种情况下,您将有一个导出类型的模块M,但不是它的构造函数M(..)。相反,您可以导出智能构造函数来创建M具有适当权限的操作。

此外,由于模糊的技术原因,即使不导出M构造函数中,“不受信任”的代码仍然有可能在不同的权限上下文之间进行强制:

stealPermission :: M (PermissionA:ps) a -> M ps a
stealPermission = coerce

因为第一个参数M类型构造函数有一个所谓的“角色”,默认为“幻影”而不是“名义”。如果你覆盖这个:

{-# LANGUAGE RoleAnnotations #-}
type role M nominal _

then coerce只能在构造函数位于作用域内的情况下使用,从而弥补了这个漏洞。不受信任的代码仍然可以使用unsafeCoerce,但有一些机制(Google 表示“Safe Haskell”)可以防止这种情况发生。

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

不同种类的ReaderT? 的相关文章

  • 如何制作Applicative的固定长度向量实例?

    最近了解了推广 决定尝试写向量 LANGUAGE DataKinds GADTs KindSignatures module Vector where data Nat Next Nat Zero data Vector Nat gt gt
  • 机器和管道(或其他类似的库)之间的概念区别是什么?

    我想学习这个概念 以便我能够理解和使用诸如machines http hackage haskell org package machines 我试着跟随R nar Bjarnason 关于机器的演讲 https dl dropbox co
  • 如何在 Windows 7 中配置 cabal?

    我已经在Windows 7中安装了Haskell Platform 2012 我在控制台中编写cabal update我收到消息说有新版本的阴谋集团 我写的cabal install cabal install 安装完成后 它告诉我 cab
  • 在 Windows 上使用堆栈安装 SDL2 for Haskell

    我正在尝试将 SDL2 与堆栈一起使用 我跟着这些说明 https www reddit com r haskellgamedev comments 4jpthu windows sdl2 is now almost painless vi
  • 移动列表中特定元素的简单函数

    我是 Haskell 的新手 我正在尝试弄清楚如何创建一个函数 shift Eq a gt a gt a gt Int gt a shift x h t z 输入 一个通用列表和一个相同类型的元素 x 前提条件 元素x存在于列表中 Outp
  • 使用 cabal new-install 重新安装相同版本的软件包

    我正在开发 Haskell 包 我还没有上传到Hackage 版本号是0 1 0 0 我正在使用新风格的 Cabal 命令 为了在我处理包的同时测试它 使库可用于测试项目 我运行cabal new install lib构建包后 然而 我注
  • 显示未定义的实例

    可以采取任何措施来为未定义的值定义 Show 实例吗 也许存在一些 GHC 扩展 我想要这样的东西 gt print 1 undefined 1 undefined 根据Haskell 2010 报告 第 9 章 http www hask
  • 我可以获得有关过度限制类型签名的警告吗?

    当我为可能更具多态性的函数提供类型签名时 GHC 或某些 lint 工具可以告诉我吗 GHC 不这样做 快速搜索 Hackage 也没有发现任何结果 实现这样的事情的一个简单但可能非常有效的方法是在 GHCi 中加载模块 使用 browse
  • 在 Haskell 中将字节转换为 Int64s/Floats/Doubles

    我正在尝试解析 Haskell 中的二进制文件格式 Apple 的二进制属性列表格式 该格式所需的内容之一是将字节序列视为 a 无符号 1 2 或 4 字节整数 b 有符号 8 字节整数 c 32 位floats d 64 位doubles
  • 通过 Emacs 评估 ghci 或 Hugs 中的缓冲区

    在 Emacs 中使用 sml mode 我已经能够使用以下命令将缓冲区内容直接发送到较差的 SML 进程C c C b 现在我只想用 Haskell 做同样的事情 Haskell 模式似乎不支持这一点 所以我想知道 使用 Emacs 和
  • 类 GADT 类型变量的未来角色?

    A 昨天的问题 https stackoverflow com q 41135212 3072788有一个定义HList 来自HList https hackage haskell org package HList 0 4 1 0 doc
  • ErrorT 已弃用,但 exceptT 不适合

    我有一个一元计算 在某些时候 由于单子模式匹配 它开始需要 MonadFail 约束 我的简单解决方法是使用以下命令运行它 fmap either error id runErrorT 然而哎呀 Deprecated Use Control
  • 如何从 haskell 中的 IOError 获取 errno?

    我在 haskell 平台上 GHC 6 12 1 作为 apt get 安装在 Debian Squeeze 上 鉴于我需要在与最初引发它的线程不同的线程上使用它 如何从 IOError 中获取底层 errno 我需要这个的原因是因为我正
  • Haskell 中的实例声明

    我有这两个功能 primes sieve 2 where sieve p xs p sieve x x lt xs x mod p gt 0 isPrime number number 1 null x x lt takeWhile x g
  • 在 Haskell 中增长数组

    我想在 Haskell 中实现以下 命令式 算法 给定一个序列对 e0 s0 e1 s1 e2 s2 en sn 其中 e 和 s 部分不一定是自然数不同的是 在每个时间步都会随机选择该序列的一个元素 例如 ei si 并根据 ei si
  • 这个记忆的斐波那契函数是如何工作的?

    在我正在做的函数式编程课程的当前练习作业中 我们必须制作给定函数的记忆版本 为了解释记忆化 给出以下示例 fiblist fibm x x lt 0 fibm 0 0 fibm 1 1 fibm n fiblist n 1 fiblist
  • 如何手动推断表达式的类型

    给定 Haskell 函数 head filter fst 现在的问题是如何手动 手动 找到类型 如果我让 Haskell 告诉我我得到的类型 head filter fst Bool b gt Bool b 但我想了解仅使用所用函数的签名
  • 将两个 Int 值相除以获得 Float 的正确方法是什么?

    我想分两份IntHaskell 中的值并获得结果Float 我尝试这样做 foo Int gt Int gt Float foo a b fromRational a b 但 GHC 版本 6 12 1 告诉我 无法将预期类型 Intege
  • “Eta减少”并不总是在Haskell中举行?

    我发现我可以说 LANGUAGE RankNTypes f1 forall b b gt b gt forall c c gt c f1 f id f HLint 告诉我我可以在这里做 Eta 减少 但是 f2 forall b b gt
  • 以下两个 lambda 函数的空间复杂度

    我正在阅读以下内容 https en wikibooks org wiki Haskell Graph reduction https en wikibooks org wiki Haskell Graph reduction 其内容如下

随机推荐

  • 防止同一用户在桌面应用程序上登录

    我正在开发一个多用户桌面应用程序 我需要找到一种方法 让每个用户名一次只允许一个用户登录 基本上 John 无法从 PC A 登录应用程序 然后再运行到 PC B 并登录 我的想法是设置一个位标志来指示用户已登录 并且当从另一个位置进行尝试
  • 为什么Python中set的大小可以比dict大?

    为什么a的大小是set比一个大dict s set d for i in range 20 s add i d i 1 print f i 1 s sizeof d sizeof Output 17 712 624 18 712 624 1
  • 如何在 Google Dataproc 集群中安装 python 包

    创建并运行集群后 是否可以在 Google Dataproc 集群中安装 python 包 我尝试使用 pip install xxxxxxx 在主命令行中 但它似乎不起作用 Google的Dataproc文档没有提及这种情况 创建集群后
  • 按组添加ID列[重复]

    这个问题在这里已经有答案了 我想根据两列纬度和经度在 R 中创建一个唯一 ID 以便重复的位置具有相同的集群 ID 例如 LAT LONG Cluster ID 13 5330 15 4180 1 13 5330 15 4180 1 13
  • MemoryCache 的大小设置意味着什么?

    在控制器类中 我有 using Microsoft Extensions Caching Memory private IMemoryCache cache private readonly MemoryCacheEntryOptions
  • PHP 创建日期范围

    以这种格式的日期开始 2011 05 01 09 00 00 如何创建一个包含一年中所有工作日 因此排除所有周六和周日 的所有办公时间 09 00 到 17 00 的数组 我想要达到的是这样的 2011 05 01 09 00 00 201
  • 重置远程跟踪分支

    如何将远程跟踪分支的分支指针重置为其之前的提交之一 这样看起来我没有拉动 类似于本地分支的硬重置 您可以使用以下命令设置对任何其他提交的引用 git update ref refs remotes origin master
  • 如何从 VB-6 旧应用程序中启动屏幕键盘程序

    我正在尝试从 Windows 10 32 或 Windows 10 64 位计算机上的 VB 6 应用程序中 shell osk exe 过去我们只是简单地使用 Private Sub Command1 Click Dim strTemp
  • 您能否将 TeamCity“工件”配置为重新检入源代码管理?

    我希望将 TeamCity Artifacts 重新签入源代码管理 我以前使用过 TFS Integrator 所以我习惯了它的方法 通过配置 它可以将输出构建签入源代码管理 然后 当人们进行 get 操作时 他们将收到这些文物 或者是否有
  • 指定共享对象(共享库)的名称

    Go 编译器为共享对象生成的名称是错误的 例子 go install buildmode shared linkshared github com apache thrift lib go thrift code google com p
  • Windows 安装程序和安装应用程序合并到一个文件中?

    我使用 Visual Studio 在 C 中编写了一个应用程序 我创建了一个项目设置文件 该文件在我的调试中为我创建了文件 Windows Installer 和安装应用程序都是必需的 但我想将它们合并为一个 就像当您下载应用程序时 安装
  • java中删除文件中的一行

    好的 所以我尝试使用 java lang String 从文本文件中删除行 目前我这样做的方式是跟踪行号并输入索引 索引是我要删除的行 因此 每次读取新的数据行时 我都会增加行数 现在 当我达到相同索引的行数时 我不会将数据写入临时文件 现
  • asp.net mvc相当于rails回调before_save

    您好 我正在寻找一个 ASP NET MVC 回调 以便在保存模型之前获取详细数据 Rails 中有 before save Thanks 如果您正在使用实体框架 您的标签表明 那么这个 StackOverflow 帖子 https sta
  • Async CTP 可以与可移植库一起使用吗

    我想看看异步 CTP 是否带有可移植类库 不可以 异步 CTP 对于桌面 电话 Silverlight 4 和 Silverlight 5 有不同的 dll 希望在不久的将来 所有这些平台都将原生支持任务 和异步 然后可移植类库将能够使用异
  • 依赖注入和项目参考[重复]

    这个问题在这里已经有答案了 我正在尝试了解 DI 以便更好地了解 IoC 以及其他好处 在 DI 之前 我有一个项目 其中包含一个 UI 项目 MVC 一个 BusinessLogic 项目和一个 DataAccess 项目 我还有一个 S
  • 当 Django 在 postgresql 中使用可序列化事务隔离级别时,哪些具体异常代表序列化失败?

    有时 对于 Django 中的数据库操作 需要使用比默认 已提交读 更高的隔离级别 文档警告 https docs djangoproject com en 1 11 ref databases isolation level that 在
  • StringBuilder 在 C# 中的性能?

    我有一个StringBuilder对象 我在其中添加一些字符串 如下所示 我想知道哪种方法更好 第一个是 StringBuilder sb new StringBuilder sb Append Hello How are you 第二个是
  • 使用 XSLT 转换 XML 并保留 Unicode 字符

    我的 XSLT 转换已经成功了几个月 直到我遇到带有 Unicode 字符 很可能是表情符号 的 XML 文件 我需要保留 Unicode 但 XSLT 正在将其转换为 HTML 实体 我认为将编码设置为 UTF 8 可以解决我的问题 但我
  • 为什么 copyTo(... PASTE_VALUES) 在宏中间不起作用?

    我长期使用的电子表格技术之一是就地复制 粘贴特殊值 C PSV 使用公式生成我感兴趣的值后 I C PSV 然后可以删除源数据 所以我写了一个使用这种技术的宏 但单元格最终是空的 但如果我将宏分成两个 在 C PSV 之前结束第一个宏 那么
  • 不同种类的ReaderT?

    冒着成为一个XY问题 https en wikipedia org wiki XY problem 是否有可能有一个ReaderT与不同的环境 我正在尝试类似 type AppM perms ReaderT perms IO 但是编译器抱怨