使用在读取后不会改变的参数

时间:2014-03-22 04:17:24

标签: haskell monads purely-functional pure-function

我正在学习Haskell并编写一个解决玩具问题的程序。该程序使用参数 k ,它在运行时不会发生变化,在从文件读取参数后。我对使用纯函数非常陌生,我想尽可能多地编写纯函数。

我有一个数据类型Node,用于比较节点,获取节点的后代等等。目前,所有这些函数都将参数 k 作为参数,例如

compare k node1 node2 = ...
desc k node = ...

每当我必须递归调用函数中的任何一个时,我必须重复 k 参数。这似乎是多余的,因为 k 对于这些函数永远不会有不同的值,因为它使类型签名的可读性降低,并且如果可能的话我想重构它。

是否有任何策略可以使用纯函数执行此操作,还是仅仅是我必须处理的限制?

我所想到的

之前我在顶层对 k 进行了硬编码,它似乎有效(我能够在函数中使用 k 而不需要它作为显式参数)。但是,一旦我需要从文件中读取输入,这显然是不可行的。

另一种可能的策略是在main函数中定义所有这些函数,但在Haskell中似乎强烈建议不要这样做。

4 个答案:

答案 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扩展名。这涉及定义comparedesck作为隐式参数:

{-# 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 ...

请注意,在任何一种情况下,您都无法致电comparedesc,直到您确定了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的答案),尽管这对您的用例来说听起来很重要。