使用仿函数来表示全局变量?

时间:2011-02-25 17:01:15

标签: haskell functor

我正在学习Haskell,并正在为一个类实现一个算法。它工作正常,但是类的要求是我保持计数乘以或加两个数的总次数。这就是我在其他语言中使用全局变量的原因,我的理解是它对Haskell来说是诅咒。

一个选项是让每个函数返回此数据及其实际结果。但这似乎并不好玩。

这就是我的想法:假设我有一些功能f :: Double -> Double。我可以创建数据类型(Double, IO)然后使用仿函数来定义(Double, IO)的乘法来进行乘法并向IO写入内容。然后我就可以将我的新数据传递给我的函数了。

这有什么意义吗?有更简单的方法吗?

编辑:为了更清楚,在OO语言中,我将声明一个继承自Double的类,然后覆盖*操作。这将允许我不必重写我的函数的类型签名。我想知道在Haskell中是否有某种方法可以做到这一点。

具体来说,如果我定义f :: Double -> Double,那么我应该能够functor :: (Double -> Double) -> (DoubleM -> DoubleM)对吗?然后我可以保持我的功能与现在一样。

4 个答案:

答案 0 :(得分:5)

实际上,你的第一个想法(用每个值返回计数)并不是坏的,并且可以由Writer monad(来自mtl包中的Control.Monad.Writer或来自Control.Monad.Trans.Writer的{​​{1}}更抽象地表达)变形金刚包)。本质上,编写器monad允许每个计算都有一个相关的“输出”,只要它是Monoid的一个实例,它就可以是任何东西 - 一个定义的类:

  • 空输出(mempty),即分配给'return'的输出
  • 组合输出的关联函数(`mappend'),用于排序操作

在这种情况下,输出是一个操作计数,'empty'值是零,并且组合操作是加法。例如,如果您要单独跟踪操作:

data Counts = Counts { additions: Int, multiplications: Int }

将该类型设为Monoid的实例(位于模块Data.Monoid中),并将您的操作定义为:

add :: Num a => a -> a -> Writer Counts a
add x y = do
    tell (Counts {additions = 1, multiplications = 0})
    return (x + y)

作家monad与你的Monoid实例一起,负责将所有'tell'传播到顶层。如果您愿意,您甚至可以为Num实现Num a => Writer Counts a实例(或者,最好是为新类型实现,因此您不创建孤立实例),这样您就可以使用常规数值运算符

答案 1 :(得分:4)

以下是为此目的使用Writer的示例:

import Control.Monad.Writer
import Data.Monoid
import Control.Applicative -- only for the <$> spelling of fmap

type OpCountM = Writer (Sum Int)

add :: (Num a) => a -> a -> OpCountM a
add x y = tell (Sum 1) >> return (x+y)

mul :: (Num a) => a -> a -> OpCountM a
mul x y = tell (Sum 1) >> return (x*y)

-- and a computation
fib :: Int -> OpCountM Int
fib 0 = return 0
fib 1 = return 1
fib n = do
    n1 <- add n (-1)
    n2 <- add n (-2)
    fibn1 <- fib n1
    fibn2 <- fib n2
    add fibn1 fibn2

main = print (result, opcount)
  where
  (result, opcount) = runWriter (fib 10)

对于纤维的定义是相当漫长而丑陋的... monadifying可能是一种痛苦。使用适用符号可以使其更简洁:

fib 0 = return 0
fib 1 = return 1
fib n = join (fib <$> add n (-1) <*> add n (-2))

但是对于初学者来说,这是不透明的。在你对Haskell的习语很满意之前,我不会推荐这种方式。

答案 2 :(得分:1)

你在学习什么级别的Haskell?可能有两个合理的答案:让每个函数像你建议的那样返回其计数及其返回值,或者(更高级)使用monad(例如State)来保持计数在后台。你也可以写一个特殊用途的monad来保持计数;我不知道这是否是你教授的意图。使用IO作为可变变量并不是解决问题的优雅方法,并不是您需要的。

答案 3 :(得分:1)

除了显式返回元组或使用状态monad之外,另一种解决方案可能是将其包装在数据类型中。类似的东西:

data OperationCountNum = OperationCountNum Int Double deriving (Show,Eq)

instance Num OperationCountNum where
    ...insert appropriate definitions here

Num类定义了数字上的函数,因此您可以在OperationCountNum类型上定义函数+,* etc,以便跟踪生成每个数字所需的操作数。

这样,计算操作将被隐藏,您可以使用正常的+,* etc操作。您只需要在开始时将数字包装在OperationCountNum类型中,然后在结尾处提取它们。

在现实世界中,这可能不是你怎么做的,但它的优势在于使代码更容易阅读(没有明确的分解和元组)并且相当容易理解。