我正在学习Haskell并编写一个解决玩具问题的程序。该程序使用参数 k ,它在运行时不会发生变化,在从文件读取参数后。我对使用纯函数非常陌生,我想尽可能多地编写纯函数。
我有一个数据类型Node
,用于比较节点,获取节点的后代等等。目前,所有这些函数都将参数 k 作为参数,例如
compare k node1 node2 = ...
desc k node = ...
每当我必须递归调用函数中的任何一个时,我必须重复 k 参数。这似乎是多余的,因为 k 对于这些函数永远不会有不同的值,因为它使类型签名的可读性降低,并且如果可能的话我想重构它。
是否有任何策略可以使用纯函数执行此操作,还是仅仅是我必须处理的限制?
我所想到的
之前我在顶层对 k 进行了硬编码,它似乎有效(我能够在函数中使用 k 而不需要它作为显式参数)。但是,一旦我需要从文件中读取输入,这显然是不可行的。
另一种可能的策略是在main
函数中定义所有这些函数,但在Haskell中似乎强烈建议不要这样做。
答案 0 :(得分:5)
通常的Haskell方法是使用Reader
monad。考虑Reader
的一种方法是它提供对环境的访问的计算。它可以定义为
newtype Reader r a = Reader { runReader :: r -> a }
所以你的函数会有类型
compare :: Node -> Node -> Reader k Ordering -- or Bool, or whatever the return value is
desc :: Node -> Reader k String -- again, guessing at the output type.
在Reader
计算中,使用函数ask :: Reader r r
来访问参数。
在顶层,您可以使用Reader
runReader theComputation env
计算
这通常比明确传递参数更好。首先,任何不需要环境的功能都可以写成普通功能,而不必将其作为参数。如果它调用另一个使用环境的函数,那么monad将自动提供它,而不需要你做额外的工作。
您甚至可以定义类型同义词
type MyEnv = Reader Env
并将其用于您的函数类型签名。然后,如果您需要更改环境,则只需更改一种类型,而不是更改所有类型的签名。
来自standard libraries的定义处理monad变换器有点复杂,但它的工作方式与这个更简单的版本相同。
答案 1 :(得分:4)
最终你必须在任何需要的地方传递k
的值,但是你可以采取一些措施来避免重复它。
您可以做的一件事是在知道k
的值后定义便利函数:
myfunc = let k = ...
compare' = compare k
desc' = desc k
in ...
(use compare' and desc' here)
另一种方法是使用Implicit Parameters扩展名。这涉及定义compare
和desc
以k
作为隐式参数:
{-# LANGUAGE ImplicitParameters #-}
compare :: (?k :: Int) => Node -> Node
compare n1 n2 = ... (can use ?k here) ...
desc :: (?k :: Int) => Node
desc = ... (can use ?k here) ...
myfunc = let ?k = ...
in ... use compare and desc ...
请注意,在任何一种情况下,您都无法致电compare
或desc
,直到您确定了k
为止。
答案 2 :(得分:0)
这就是我喜欢使用不会改变的值来构造递归函数的方法
map f xs = map' xs
where map' (x:xs) = f x : map' xs
答案 3 :(得分:0)
两个带有本地函数定义的简单技巧可能很有用。首先,只需使用范围:
,就可以在递归定义中隐含k
desc :: Int -> Node -> [Node]
desc k node = desc' node
where
desc' node = -- Carry on; k is in scope now.
其次,如果要在同一范围内使用相同的k
多次调用函数,则可以对部分应用的函数使用本地定义:< / p>
main = do
k <- getKFromFile
-- etc.
let desc' = desc k -- Partial application.
descA = desc' nodeA
descB = desc' nodeB
print [descA, descB]
正确的隐式参数传递通常使用Reader monad(或者可以说是模拟)与Reader monad完成(请参阅John的答案),尽管这对您的用例来说听起来很重要。