2

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

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

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

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

是否有任何策略可以使用纯函数来做到这一点,或者这仅仅是我必须处理的限制?

我所想到的

早些时候我在顶层硬编码k ,它似乎工作(我能够在函数中使用k而不需要它作为显式参数)。但是,一旦我需要从文件中读取输入,这显然是不可行的。

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

4

4 回答 4

5

通常的 Haskell 方法是使用Readermonad。一种思考方式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

并将其用于您的函数类型签名。然后如果你需要改变环境,你只需要改变一种类型,而不是改变你所有的类型签名。

标准库中的定义处理 monad 转换器有点复杂,但它的工作方式与这个更简单的版本相同。

于 2014-03-22T05:02:35.667 回答
4

最终,您必须在需要的任何地方传递值k,但是您可以采取一些措施来避免重复。

您可以做的一件事是在k已知值后定义便利函数:

myfunc = let k = ...
             compare' = compare k
             desc'    = desc k
         in ...
            (use compare' and desc' here)

另一种方法是使用隐式参数扩展。这涉及定义comparedesc作为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是。

于 2014-03-22T05:24:59.800 回答
0

这就是我喜欢用不变的值构造递归函数的方式

map f xs = map' xs
  where map' (x:xs) = f x : map' xs
于 2014-03-22T05:28:46.033 回答
0

两个可能有用的局部函数定义的简单技巧。首先,您可以k通过使用范围来在递归定义中进行隐式处理:

desc :: Int -> Node -> [Node]
desc k node = desc' node
    where
    desc' node = -- Carry on; k is in scope now.

k其次,如果您要在同一范围内多次调用您的函数,您可以为部分应用的函数使用本地定义:

main = do
    k <- getKFromFile
    -- etc.
    let desc' = desc k -- Partial application.
        descA = desc' nodeA
        descB = desc' nodeB
    print [descA, descB]

正确的隐式参数传递通常使用 Reader monad 完成(或者可以说是模拟的)(请参阅 John L 的答案),尽管这对于您的用例来说听起来有点重量级。

于 2014-03-22T05:30:31.113 回答