开发者

Using functors for global variables?

I'm learning Haskell, and am implementing an algorithm for a class. It works fine, but a requirement of the class is that I keep a count of the total number of times I multiply or add two numbers. This is what I would use a global variable for in other languages, and my understanding is that it's anathema to Haskell.

One option is to just have each function retur开发者_开发百科n this data along with its actual result. But that doesn't seem fun.

Here's what I was thinking: suppose I have some function f :: Double -> Double. Could I create a data type (Double, IO) then use a functor to define multiplication across a (Double, IO) to do the multiplication and write something to IO. Then I could pass my new data into my functions just fine.

Does this make any sense? Is there an easier way to do this?

EDIT: To be more clear, in an OO language I would declare a class which inherits from Double and then override the * operation. This would allow me to not have to rewrite the type signature of my functions. I'm wondering if there's some way to do this in Haskell.

Specifically, if I define f :: Double -> Double then I should be able to make a functor :: (Double -> Double) -> (DoubleM -> DoubleM) right? Then I can keep my functions the same as they are now.


Actually, your first idea (return the counts with each value) is not a bad one, and can be expressed more abstractly by the Writer monad (in Control.Monad.Writer from the mtl package or Control.Monad.Trans.Writer from the transformers package). Essentially, the writer monad allows each computation to have an associated "output", which can be anything as long as it's an instance of Monoid - a class which defines:

  • The empty output (mempty), which is the output assigned to 'return'
  • An associative function (`mappend') that combines outputs, which is used when sequencing operations

In this case, your output is a count of operations, the 'empty' value is zero, and the combining operation is addition. For example, if you're tracking operations separately:

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

Make that type an instance of Monoid (which is in the module Data.Monoid), and define your operations as something like:

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

The writer monad, together with your Monoid instance, then takes care of propagating all the 'tells' to the top level. If you wanted, you could even implement a Num instance for Num a => Writer Counts a (or, preferably, for a newtype so you're not creating an orphan instance), so that you can just use the normal numerical operators.


Here is an example of using Writer for this purpose:

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)

That definition of fib is pretty long and ugly... monadifying can be a pain. It can be made more concise with applicative notation:

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

But admittedly more opaque for a beginner. I wouldn't recommend that way until you are pretty comfortable with the idioms of Haskell.


What level of Haskell are you learning? There are probably two reasonable answers: have each function return its counts along with its return value like you suggested, or (more advanced) use a monad such as State to keep the counts in the background. You could also write a special-purpose monad to keep the counts; I do not know if that is what your professor intended. Using IO for mutable variables is not the elegant way to solve the problem, and is not necessary for what you need.


Another solution, apart from returning a tuple or using the state monad explicitly, might be to wrap it up in a data type. Something like:

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

instance Num OperationCountNum where
    ...insert appropriate definitions here

The class Num defines functions on numbers, so you can define the functions +, * etc on your OperationCountNum type in such a way that they keep track of the number of operations required to produce each number.

That way, counting the operations would be hidden and you can use the normal +, * etc operations. You just need to wrap your numbers up in the OperationCountNum type at the start and then extract them at the end.

In the real world, this probably isn't how you'd do it, but it has the advantage of making the code easier to read (no explicit detupling and tupling) and being fairly easy to understand.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜