4

我正在使用一个在方便的 ReaderT monad 中携带东西的包。它们的 runReaderT/unwrapping 函数位于隐藏模块中。我希望能够在任意环境中任意/手动打开它。所以我对手动操作没有任何疑虑——但是,环境类型有一个隐藏的数据构造函数,所以我无法生成一个使用 runReaderT 进行类型检查的环境。

有什么办法可以解决这个问题吗?还是我应该放弃这个不可能并尝试重组我的程序?我计划将此项目部署到具有自动构建系统的远程系统,因此我不确定是否要直接编辑包的源代码/分叉它。

具体来说,我正在使用scotty,并且正在尝试runActionMWeb.Scotty.Action模块中进行模拟。我试图模拟页面请求并捕获结果,并模拟通常具有 IO 副作用的页面请求。

具体例子:

-- LibraryPackage.hs
module LibraryPackage (SpecialReader) where

import Control.Monad.Reader

type SpecialReader a = Reader Env a
data Env = Env Int

runSpecialReader r = runReader r (Env 5)


-- Main.hs
import LibraryPackage

main = do
  let
    r = return 10 :: SpecialReader Int

  -- problem here -- cannot construct an `Env`
  print $ runReader r (Env 5)
4

5 回答 5

9

你很好,真的被困住了。这是选项

  1. 与包维护者交谈,看看是否有其他解决方案
  2. undefined如果您实际上不需要有意义的值,请使用
  3. 不要使用这个版本的库(剪切和粘贴,或者在非常极端的情况下分叉)
  4. 如果这只是一次性的,您可以牺牲一只山羊并尝试unsafeCoerce

Haskell 不允许你做一些事情,比如创建你不应该做的类型。想象一下,例如,如果您可以进行模式匹配,会有什么原因IO

于 2013-09-10T18:14:19.307 回答
2

许多包使用模块系统来提供安全保证或优化,如果隐藏对象能够被客户端代码以任意方式使用(最明显的例子是IO!),这是不可能的。模块系统旨在使您无法完成您想做的事情。

然而,许多包也有“秘密”的暴露模块(通常Internal在名称中),无论如何都会导出隐藏的名称。这些通常不会出现在官方文档中,但您会在 source/cabal 文件中看到它们。

如果 Scotty 是这样一个包,那么你很幸运,但是使用内部模块很棘手。像对待函数族一样对待它们unsafe*;一般来说,它们会导致事情发生严重错误,您有责任确保在这种特殊情况下不会发生这种情况。毕竟,for 的构造函数Env可能被故意隐藏,以阻止您在任意环境中运行 monad 堆栈,因为那样会出错。此外,包作者可能不会认为对内部模块的重大更改是“向后不兼容”的,因此即使在对 Scotty 的次要版本更新时,您也可能必须对代码进行转发端口。

如果真的没有模块导出所需的构造函数(甚至没有一个未公布的构造函数),那么如果不更改包的源代码就无法获得它。您可以要求将其公开(公开或作为后门),或分叉包,或尝试重新设计您的代码以使您不需要它。

于 2013-09-10T21:51:16.457 回答
1

另一种选择是Language.Haskell.TH.Syntax.mkNameG。它可能和 一样糟糕unsafeCoerce,但这里有一个示例http://lpaste.net/91403(Minimize构造函数是/未导出)。

于 2013-09-11T22:57:35.620 回答
1

你可以试试unsafeCoerse。如果Env是 eg 上的新类型Int,那么unsafeCoerse 5 :: Env应该可以工作(至少ghc可以保证)。

它可能适用于 ADT,也可能不适用于 ADT。

shum@shum-lt:~$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :m + Unsafe.Coerce 
Prelude Unsafe.Coerce> data Env1 = Env1 Int String deriving Show
Prelude Unsafe.Coerce> data Env2 = Env2 Int String deriving Show
Prelude Unsafe.Coerce> unsafeCoerce (Env1 5 "hello") :: Env2
Env2 5 "hello"
Prelude Unsafe.Coerce> 
于 2013-09-10T18:59:59.377 回答
1

I've had a similar issue a few times and I haven't been able to find any other solution than copy-pasting the code you need into your own module and export the necessary funcs.

It is a pretty ugly solution but it does work in the lack of anything else.

于 2013-09-10T17:56:42.740 回答