本文介绍了在一个函数中访问环境的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 main 我可以读取我的配置文件,并以 runReader(somefunc)myEnv 的形式提供。但 somefunc 不需要访问读者提供的 myEnv ,也不需要访问链中的下一对。 如何在函数中访问环境而不用将所有介入函数标记为(Reader Env)?这不可能是正确的,否则你首先会把myEnv传递给你。通过多层次的函数传递未使用的参数只是丑陋的(是不是?)。 有很多我可以在网上找到的例子,但它们看起来都很像在runreader和访问环境之间只有一个级别。 我接受Chris Taylor,因为它是最彻底的我可以看到它对其他人有用。感谢Heatsink谁是唯一一个试图直接回答我的问题的人。 对于这个测试应用程序,我可能会完全抛弃Reader,并通过周围的环境。它不会给我买任何东西。 我必须说,我仍然困惑于向函数h提供静态数据的想法不仅改变了它的类型签名,还改变了那些g称之为f,g称之为g。所有这些,即使涉及的实际类型和计​​算都没有改变。看起来好像执行细节泄露了整个代码没有真正的好处。 给所有返回类型 Reader Env a ,尽管这并不像你想象的那么糟糕。所有东西都需要这个标签的原因是,如果 f 取决于环境: type Env = Int f :: Int - > Reader Int Int fx = do env< - ask return(x + env) 和 g 调用 f : < code> p> > 然后 g 也取决于环境 - 行 y 可以是不同的,这取决于传入的环境,所以 g 的适当类型是 g :: Int - > Reader Int Int 这实际上是一件好事!类型系统迫使你明确地识别你的函数依赖于全球环境的地方。您可以通过定义短语 Reader Int 的快捷键来节省自己的打字痛苦: type Global = Reader Int 现在您的类型注释是: f,g :: Int - > Global Int 这样可读性更强。 另一种方法是明确地将环境传递给所有函数: f :: Env - > Int - > Int f env x = x + env g :: Env - > Int - > Int gx = x +(f env x) 这可以工作, - 它不会比使用 Reader monad差。当你想扩展语义时,就会遇到困难。假设您还依赖于具有 Int 类型的可更新状态来计算函数应用程序。现在您必须将您的函数更改为: type Counter = Int f :: Env - > ;计数器 - > Int - > (Int,Counter) f env counter x =(x + env,counter + 1) g :: Env - >计数器 - > Int - > (Int,Counter)g env counter x = let(y,newcounter)= f env counter x in(x + y,newcounter + 1) 这肯定不太愉快。另一方面,如果我们采用一元方法,我们只需重新定义 类型Global = ReaderT Env(状态计数器) f 和 g 继续工作没有任何问题。要将它们更新为具有应用程序计数语义,我们只需将它们更改为 f :: Int - > Global int f x = do modify(+1) env< - ask return(x + env) g :: Int - > Global Int gx = do modify(+1)y return(x + y) 并且他们现在完美地工作。比较这两种方法: 当我们想为程序添加新功能时,显式传递环境和状态需要完全重写。 使用一个monadic接口需要改变三行 - 即使我们改变了第一行,程序仍然可以工作,这意味着我们可以做增量重构(并在每次更改后对其进行测试),从而降低重构引入新bug的可能性。 In main I can read my config file, and supply it as runReader (somefunc) myEnv just fine. But somefunc doesn't need access to the myEnv the reader supplies, nor do the next couple in the chain. The function that needs something from myEnv is a tiny leaf function.How do I get access to the environment in a function without tagging all the intervening functions as (Reader Env)? That can't be right because otherwise you'd just pass myEnv around in the first place. And passing unused parameters through multiple levels of functions is just ugly (isn't it?).There are plenty of examples I can find on the net but they all seem to have only one level between runReader and accessing the environment.I'm accepting Chris Taylor's because it's the most thorough and I can see it being useful to others. Thanks too to Heatsink who was the only one who attempted to actually directly answer my question.For the test app in question I'll probably just ditch the Reader altogether and pass the environment around. It doesn't buy me anything.I must say I'm still puzzled by the idea that providing static data to function h changes not only its type signature but also those of g which calls it and f which calls g. All this even though the actual types and computations involved are unchanged. It seems like implementation details are leaking all over the code for no real benefit. 解决方案 You do give everything the return type of Reader Env a, although this isn't as bad as you think. The reason that everything needs this tag is that if f depends on the environment:type Env = Intf :: Int -> Reader Int Intf x = do env <- ask return (x + env)and g calls f:g x = do y <- f x return (x + y)then g also depends on the environment - the value bound in the line y <- f x can be different, depending on what environment is passed in, so the appropriate type for g isg :: Int -> Reader Int IntThis is actually a good thing! The type system is forcing you to explicitly recognise the places where your functions depend on the global environment. You can save yourself some typing pain by defining a shortcut for the phrase Reader Int:type Global = Reader Intso that now your type annotations are:f, g :: Int -> Global Intwhich is a little more readable.The alternative to this is to explicitly pass the environment around to all of your functions:f :: Env -> Int -> Intf env x = x + envg :: Env -> Int -> Intg x = x + (f env x)This can work, and in fact syntax-wise it's not any worse than using the Reader monad. The difficulty comes when you want to extend the semantics. Say you also depend on having an updatable state of type Int which counts function applications. Now you have to change your functions to:type Counter = Intf :: 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)which is decidedly less pleasant. On the other hand, if we are taking the monadic approach, we simply redefinetype Global = ReaderT Env (State Counter)The old definitions of f and g continue to work without any trouble. To update them to have application-counting semantics, we simply change them tof :: Int -> Global Intf x = do modify (+1) env <- ask return (x + env)g :: Int -> Global Intg x = do modify(+1) y <- f x return (x + y)and they now work perfectly. Compare the two methods:Explicitly passing the environment and state required a complete rewrite when we wanted to add new functionality to our program.Using a monadic interface required a change of three lines - and the program continued to work even after we had changed the first line, meaning that we could do the refactoring incrementally (and test it after each change) which reduces the likelihood that the refactor introduces new bugs. 这篇关于在一个函数中访问环境的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
11-02 13:28