访问函数中的环境

Ric*_*ton 11 haskell

main我可以读取我的配置文件,并提供它runReader (somefunc) myEnv就好了.但是somefunc不需要访问myEnv读卡器耗材,也不需要链接中的下一对.需要来自myEnv的功能是一个很小的叶子功能.

如何在不标记所有干预功能的情况下访问函数中的环境(Reader Env)?这可能不对,因为否则你只是首先传递myEnv.通过多个级别的函数传递未使用的参数只是丑陋(不是吗?).

我可以在网上找到很多例子,但它们似乎在runReader和访问环境之间只有一个级别.


我接受克里斯泰勒的,因为它是最彻底的,我可以看到它对其他人有用.还要感谢Heatsink,他是唯一一个试图直接回答我问题的人.

对于有问题的测试应用程序,我可能只是放弃了阅读器并传递环境.它不会给我任何东西.

我必须说我仍然感到困惑的是,为函数h提供静态数据不仅改变了它的类型签名,还改变了调用它的g和调用g的f.即使所涉及的实际类型和计​​算没有改变,所有这些都是如此.似乎实现细节在整个代码中泄漏,没有真正的好处.

Chr*_*lor 8

确实给出了所有返回类型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)

旧的定义fg继续工作没有任何麻烦.要将它们更新为具有应用程序计数语义,我们只需将它们更改为

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接口需要更改三行 - 即使在我们更改了第一行之后程序仍继续工作,这意味着我们可以逐步进行重构(并在每次更改后进行测试),这降低了重构引入的可能性新的错误.


Lou*_*man 5

不.你完全将所有干预功能标记为Reader Env,或者至少在某些具有Env环境的monad中运行.而且它完全传遍各地.这是完全正常的 - 虽然没有您想象的那么低效,编译器通常会在很多地方优化这些东西.

基本上,任何使用Readermonad的东西- 即使它非常遥远 - 应该是一个Reader本身.(如果某些东西不使用Readermonad,并且没有调用其他任何东西,那么它不一定是a Reader.)

也就是说,使用Readermonad意味着你不必明确地传递环境- 它由monad自动处理.

(请记住,它只是指向环境的指针,而不是环境本身,所以它非常便宜.)