在main我可以读取我的配置文件,并提供它runReader (somefunc) myEnv就好了.但是somefunc不需要访问myEnv读卡器耗材,也不需要链接中的下一对.需要来自myEnv的功能是一个很小的叶子功能.
如何在不标记所有干预功能的情况下访问函数中的环境(Reader Env)?这可能不对,因为否则你只是首先传递myEnv.通过多个级别的函数传递未使用的参数只是丑陋(不是吗?).
我可以在网上找到很多例子,但它们似乎在runReader和访问环境之间只有一个级别.
我接受克里斯泰勒的,因为它是最彻底的,我可以看到它对其他人有用.还要感谢Heatsink,他是唯一一个试图直接回答我问题的人.
对于有问题的测试应用程序,我可能只是放弃了阅读器并传递环境.它不会给我任何东西.
我必须说我仍然感到困惑的是,为函数h提供静态数据不仅改变了它的类型签名,还改变了调用它的g和调用g的f.即使所涉及的实际类型和计算没有改变,所有这些都是如此.似乎实现细节在整个代码中泄漏,没有真正的好处.
你确实给出了所有返回类型Reader Env a,尽管这并不像你想象的那么糟糕.一切都需要这个标签的原因是,如果f取决于环境:
type Env = Int
f :: Int -> Reader Int Int
f x = do
env <- ask
return (x + env)
Run Code Online (Sandbox Code Playgroud)
和g电话f:
g x = do
y <- f x
return (x + y)
Run Code Online (Sandbox Code Playgroud)
然后g还取决于环境 - 行中的值y <- f x可以是不同的,具体取决于传入的环境,因此适当的类型g是
g :: Int -> Reader Int Int
Run Code Online (Sandbox Code Playgroud)
这实际上是件好事!类型系统强制您明确识别函数依赖于全局环境的位置.您可以通过定义短语的快捷方式来节省一些打字痛苦Reader Int:
type Global = Reader Int
Run Code Online (Sandbox Code Playgroud)
所以现在你的类型注释是:
f, g :: Int -> Global Int
Run Code Online (Sandbox Code Playgroud)
这有点可读性.
替代方法是明确地将环境传递给您的所有函数:
f :: Env -> Int -> Int
f env x = x + env
g :: Env -> Int -> Int
g x = x + (f env x)
Run Code Online (Sandbox Code Playgroud)
这可以工作,事实上在语法方面它并不比使用Readermonad 更糟糕.当您想要扩展语义时,就会遇到困难.假设您还依赖于具有可更新状态的类型Int来计算函数应用程序.现在你必须将你的功能改为:
type Counter = Int
f :: Env -> Counter -> Int -> (Int, Counter)
f env counter x = (x + env, counter + 1)
g :: Env -> Counter -> Int -> (Int, Counter)
g env counter x = let (y, newcounter) = f env counter x
in (x + y, newcounter + 1)
Run Code Online (Sandbox Code Playgroud)
这显然不太令人愉快.另一方面,如果我们采用monadic方法,我们只需重新定义
type Global = ReaderT Env (State Counter)
Run Code Online (Sandbox Code Playgroud)
旧的定义f和g继续工作没有任何麻烦.要将它们更新为具有应用程序计数语义,我们只需将它们更改为
f :: Int -> Global Int
f x = do
modify (+1)
env <- ask
return (x + env)
g :: Int -> Global Int
g x = do
modify(+1)
y <- f x
return (x + y)
Run Code Online (Sandbox Code Playgroud)
他们现在工作得很好 比较两种方法:
当我们想要为程序添加新功能时,明确传递环境和状态需要完全重写.
使用monadic接口需要更改三行 - 即使在我们更改了第一行之后程序仍继续工作,这意味着我们可以逐步进行重构(并在每次更改后进行测试),这降低了重构引入的可能性新的错误.
不.你完全将所有干预功能标记为Reader Env,或者至少在某些具有Env环境的monad中运行.而且它完全传遍各地.这是完全正常的 - 虽然没有您想象的那么低效,编译器通常会在很多地方优化这些东西.
基本上,任何使用Readermonad的东西- 即使它非常遥远 - 应该是一个Reader本身.(如果某些东西不使用Readermonad,并且没有调用其他任何东西,那么它不一定是a Reader.)
也就是说,使用Readermonad意味着你不必明确地传递环境- 它由monad自动处理.
(请记住,它只是指向环境的指针,而不是环境本身,所以它非常便宜.)