Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 return 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.

like image 536
Xodarap Avatar asked Mar 13 '26 15:03

Xodarap


2 Answers

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.

like image 139
mokus Avatar answered Mar 16 '26 04:03

mokus


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.

like image 40
luqui Avatar answered Mar 16 '26 05:03

luqui