my ugly code and the Maybe monad

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

my ugly code and the Maybe monad

Simon Parry-2
hello all,

Intro: I'm fairly new to Haskell, read some tutorials/books, this is my
first real attempt at making something rather than doing tutorial
problems - I thought I'd recode some financial maths up in Haskell...see
below.

It seems to work ok (I haven't properly tested it yet) but I feel the
pvs function is just ugly.  However it seems like its a fairly common
requirement for maths modelling ie using Maybe or Error or such to
represent conditions on the input variables and then later having to
combine those 'wrapped' values with other things.

Basically it seems inelegant and I feel like I'm confusing the monadic
and non-monadic parts?

help/criticism welcome,

thanks

Simon


module TimeValueMoney1 where

--taken from Financial Numerical Recipes in C++ by B A Odegaard (2006):
--Chapter 3

import Control.Monad

--time periods - assumes now is time 0--
times :: [Int]
times = [0..]

minusOne :: Double
minusOne = -1.0

--can have eg discrete or continuous compounding
type Compounding = Double -> Int -> Maybe Double

--discounting and present value--
discreteCompounding :: Compounding
discreteCompounding yield elapsed
    | yield > minusOne = Just ( 1.0/ (1.0 + yield)^elapsed )
    | otherwise = Nothing

continuousCompounding :: Compounding
continuousCompounding yield elapsed
    | yield > minusOne = Just (exp( minusOne * yield * fromIntegral
elapsed ) )
    | otherwise = Nothing

pvs :: Compounding -> Double -> [Double] -> Maybe [Double]
pvs df yield cashflow = zipWithM ( \c -> (>>= \d -> return $ c*d ) )
cashflow discounts
    where discounts = map discount times
          discount = df yield

Reply | Threaded
Open this post in threaded view
|

my ugly code and the Maybe monad

Jan Jakubuv-2
Hi Simon,

On Tue, Aug 18, 2009 at 10:41:45PM +0100, Simon Parry wrote:
> It seems to work ok (I haven't properly tested it yet) but I feel the
> pvs function is just ugly.  However it seems like its a fairly common
> requirement for maths modelling ie using Maybe or Error or such to
> represent conditions on the input variables and then later having to
> combine those 'wrapped' values with other things.
>

I don't quite understand what is function `pvs` supposed to do ?? Anyway,
I try to guess. It seems that it just applies `(df yield)` to `times` and
then multiply the resulting values one by one with `cashflow`. So it seems
that you need to lift multiplication `(*)` to the Maybe monad in the second
argument only. You can write your own version of `liftM2` (from
`Control.Monad`) like this:

    liftM2snd f a mb = do { b <- mb; return (f a b) }

You can verify that

    liftM2snd == (fmap .)

Thus you can rewrite `pvs` as:

    pvs2 df yield cashflow = multiply cashflow discounts
        where multiply = zipWithM (fmap . (*))
              discounts = map (df yield) times

You could alternatively use the library version of `liftM2` but then you
need to ?lift? the `cashflow` list using `return`. Like this:

    pvs3 df yield cashflow = multiply (map return cashflow) discounts
        where multiply = zipWithM (liftM2 (*))
              discounts = map (df yield) times

When you take the advantage of commutativity of `*` you can write:

    pvs4 df yield = multiply discounts . map return
        where multiply = zipWithM (liftM2 (*))
              discounts = map (df yield) times

or maybe even better:

    pvs5 df yield = multiply discounts
        where multiply = zipWithM (flip $ fmap . (*))
              discounts = map (df yield) times

Anyway, note that all the `pvs` functions (including the your one) return
`Nothing` when `(df yield)` returns `Nothing` for at least one related
member of `times`. Is that what you want?

> Basically it seems inelegant and I feel like I'm confusing the monadic
> and non-monadic parts?
>

You are using this function:

    fce = \c -> (>>= \d -> return $ c*d)

which is pretty ugly and not very intuitive. Note that this is simply
`liftM2snd (*)` from above, that is, `fmap . (*)`.

> help/criticism welcome,

You might want to look at the `liftM` functions from `Control.Monad`.

Note that I have inlined the only use of `discount`. In my opinion it
improves readability. But it's up to you to judge.

I hope this helps a little. I don't know any financial stuff so maybe I
didn't understand well what is going on.

Sincerely,
    Jan.

>
> thanks
>
> Simon
>
>
> module TimeValueMoney1 where
>
> --taken from Financial Numerical Recipes in C++ by B A Odegaard (2006):
> --Chapter 3
>
> import Control.Monad
>
> --time periods - assumes now is time 0--
> times :: [Int]
> times = [0..]
>
> minusOne :: Double
> minusOne = -1.0
>
> --can have eg discrete or continuous compounding
> type Compounding = Double -> Int -> Maybe Double
>
> --discounting and present value--
> discreteCompounding :: Compounding
> discreteCompounding yield elapsed
>     | yield > minusOne = Just ( 1.0/ (1.0 + yield)^elapsed )
>     | otherwise = Nothing
>
> continuousCompounding :: Compounding
> continuousCompounding yield elapsed
>     | yield > minusOne = Just (exp( minusOne * yield * fromIntegral
> elapsed ) )
>     | otherwise = Nothing
>
> pvs :: Compounding -> Double -> [Double] -> Maybe [Double]
> pvs df yield cashflow = zipWithM ( \c -> (>>= \d -> return $ c*d ) )
> cashflow discounts
>     where discounts = map discount times
>           discount = df yield
>
> _______________________________________________
> Beginners mailing list
> [hidden email]
> http://www.haskell.org/mailman/listinfo/beginners


--
Heriot-Watt University is a Scottish charity
registered under charity number SC000278.

Reply | Threaded
Open this post in threaded view
|

my ugly code and the Maybe monad

Simon Parry-2
Thanks Jan, very helpful and you're right I am just trying to combine 2
lists; one with 'wrapped' values, one without.

> You can write your own version of `liftM2` (from
> `Control.Monad`) like this:
>
>     liftM2snd f a mb = do { b <- mb; return (f a b) }
>

so the b <- mb bit is 'unwrapping' the Maybe b to use it with the pure
function f?  I guess I didn't realise this as I've only seen it in the
IO monad, but naturally it would work with all monads.

> You can verify that
>
>     liftM2snd == (fmap .)

if I look at this in GHCi the liftM2snd acts over monads and the (fmap .) acts over functors.  
Now I'm still trying to get comfortable with simple monad manipulations so maybe I should just
read this as functors are equivalent to monads and not worry too much about it yet?  
With that in mind fmap acts to map some pure function over a 'wrapped' value?

Thanks also for the other suggestions, its always helpful to see a progression rather than
jumping in at say pvs5.

> Anyway, note that all the `pvs` functions (including the your one) return
> `Nothing` when `(df yield)` returns `Nothing` for at least one related
> member of `times`. Is that what you want?

I did want it to only perform the calc if the yield was sensible.

thanks again

Simon

On Wed, 2009-08-19 at 12:53 +0100, Jan Jakubuv wrote:

> Hi Simon,
>
> On Tue, Aug 18, 2009 at 10:41:45PM +0100, Simon Parry wrote:
> > It seems to work ok (I haven't properly tested it yet) but I feel the
> > pvs function is just ugly.  However it seems like its a fairly common
> > requirement for maths modelling ie using Maybe or Error or such to
> > represent conditions on the input variables and then later having to
> > combine those 'wrapped' values with other things.
> >
>
> I don't quite understand what is function `pvs` supposed to do ?? Anyway,
> I try to guess. It seems that it just applies `(df yield)` to `times` and
> then multiply the resulting values one by one with `cashflow`. So it seems
> that you need to lift multiplication `(*)` to the Maybe monad in the second
> argument only. You can write your own version of `liftM2` (from
> `Control.Monad`) like this:
>
>     liftM2snd f a mb = do { b <- mb; return (f a b) }
>
> You can verify that
>
>     liftM2snd == (fmap .)
>
> Thus you can rewrite `pvs` as:
>
>     pvs2 df yield cashflow = multiply cashflow discounts
>         where multiply = zipWithM (fmap . (*))
>               discounts = map (df yield) times
>
> You could alternatively use the library version of `liftM2` but then you
> need to ?lift? the `cashflow` list using `return`. Like this:
>
>     pvs3 df yield cashflow = multiply (map return cashflow) discounts
>         where multiply = zipWithM (liftM2 (*))
>               discounts = map (df yield) times
>
> When you take the advantage of commutativity of `*` you can write:
>
>     pvs4 df yield = multiply discounts . map return
>         where multiply = zipWithM (liftM2 (*))
>               discounts = map (df yield) times
>
> or maybe even better:
>
>     pvs5 df yield = multiply discounts
>         where multiply = zipWithM (flip $ fmap . (*))
>               discounts = map (df yield) times
>
> Anyway, note that all the `pvs` functions (including the your one) return
> `Nothing` when `(df yield)` returns `Nothing` for at least one related
> member of `times`. Is that what you want?
>
> > Basically it seems inelegant and I feel like I'm confusing the monadic
> > and non-monadic parts?
> >
>
> You are using this function:
>
>     fce = \c -> (>>= \d -> return $ c*d)
>
> which is pretty ugly and not very intuitive. Note that this is simply
> `liftM2snd (*)` from above, that is, `fmap . (*)`.
>
> > help/criticism welcome,
>
> You might want to look at the `liftM` functions from `Control.Monad`.
>
> Note that I have inlined the only use of `discount`. In my opinion it
> improves readability. But it's up to you to judge.
>
> I hope this helps a little. I don't know any financial stuff so maybe I
> didn't understand well what is going on.
>
> Sincerely,
>     Jan.
>
> >
> > thanks
> >
> > Simon
> >
> >
> > module TimeValueMoney1 where
> >
> > --taken from Financial Numerical Recipes in C++ by B A Odegaard (2006):
> > --Chapter 3
> >
> > import Control.Monad
> >
> > --time periods - assumes now is time 0--
> > times :: [Int]
> > times = [0..]
> >
> > minusOne :: Double
> > minusOne = -1.0
> >
> > --can have eg discrete or continuous compounding
> > type Compounding = Double -> Int -> Maybe Double
> >
> > --discounting and present value--
> > discreteCompounding :: Compounding
> > discreteCompounding yield elapsed
> >     | yield > minusOne = Just ( 1.0/ (1.0 + yield)^elapsed )
> >     | otherwise = Nothing
> >
> > continuousCompounding :: Compounding
> > continuousCompounding yield elapsed
> >     | yield > minusOne = Just (exp( minusOne * yield * fromIntegral
> > elapsed ) )
> >     | otherwise = Nothing
> >
> > pvs :: Compounding -> Double -> [Double] -> Maybe [Double]
> > pvs df yield cashflow = zipWithM ( \c -> (>>= \d -> return $ c*d ) )
> > cashflow discounts
> >     where discounts = map discount times
> >           discount = df yield
> >
> > _______________________________________________
> > Beginners mailing list
> > [hidden email]
> > http://www.haskell.org/mailman/listinfo/beginners
>
>

Reply | Threaded
Open this post in threaded view
|

my ugly code and the Maybe monad

Jan Jakubuv-2
On Wed, Aug 19, 2009 at 11:57:47PM +0100, Simon Parry wrote:

> Thanks Jan, very helpful and you're right I am just trying to combine 2
> lists; one with 'wrapped' values, one without.
>
> > You can write your own version of `liftM2` (from
> > `Control.Monad`) like this:
> >
> >     liftM2snd f a mb = do { b <- mb; return (f a b) }
> >
>
> so the b <- mb bit is 'unwrapping' the Maybe b to use it with the pure
> function f?  

That's correct. In fact it is just a syntactic abbreviation (sugar) for

    liftM2snd f a mb = mb >>= \b -> return (f a b)

You can always rewrite expressions with `do` using just `>>=`, `>>`, and
`->`. (Note that `<-` becomes the ?dot? `->`.)

> I guess I didn't realise this as I've only seen it in the IO monad, but
> naturally it would work with all monads.
>
> > You can verify that
> >
> >     liftM2snd == (fmap .)
>
> if I look at this in GHCi the liftM2snd acts over monads and the (fmap .) acts over functors.  
> Now I'm still trying to get comfortable with simple monad manipulations so maybe I should just
> read this as functors are equivalent to monads and not worry too much about it yet?  
> With that in mind fmap acts to map some pure function over a 'wrapped' value?
>

Every Monad is automatically a Functor as well. You can define `fmap` using
monadic operations:

    fmap f ma = ma >>= return . f

Anyway, for some reasons this is not done automatically. You can do it
automatically with some GHC extensions. Load this into GHCi (don't forget
the first line):

    {-# OPTIONS -XFlexibleInstances -XUndecidableInstances #-}

    instance Monad m => Functor m where
        fmap f ma = ma >>= return . f

And here we go (in my GHCi 6.10.4):

    *Main> :t fmap
    fmap :: (Monad f) => (a -> b) -> f a -> f b

Usually, however, you don't need to do this because all instances of Monad
are already instances of Functor.

Also note that

    fmap :: (Functor f) => (a -> b) -> f a -> f b

holds as well, that is, the type of `fmap` is ambiguous (because of the
GHC extensions above).

Sincerely,
    Jan.
   


--
Heriot-Watt University is a Scottish charity
registered under charity number SC000278.