Elm idiomatic code + type Maybe

Hi everyone,

I have just started playing around with Elm and functional programming. I really like the language but I do have trouble implementing very simply calculcations (which is likely due to my little experience for functional languages). Hopefully some of you experts can help me improve my understanding of the language.

I have posted the below on stackoverflow but wasn’t sure if it makes sense to post the question here as well.

My below code takes as input wacc : Float and cfs : List Float and should calculate a net preset value (i.e. for each element of cfs calculate cfs_i / (1 + wacc)^i and then calculate the sum of the values).

The code works but is very verbose and potentially not idiomatic.

My main question besides hints how to make it more concise / idiomatic is how do I change my code to be able to accept wacc and cfs of types Maybe.

Helpful for any hint / info. Thanks!

-- helper functions
zip : List a -> List b -> List (a,b) 
zip list1 list2 =
    List.map2 Tuple.pair list1 list2

calcDF : Float -> Int -> List Float
calcDF wacc n = 
    let
        waccs = List.repeat n wacc
        time = List.range 0 n |> List.map toFloat
        waccs_time = zip waccs time
    in
        List.map (\x -> 1/ (1 + Tuple.first x)^(Tuple.second x)) waccs_time

-- my npv function
calcNPV : List Float -> Float -> Html text
calcNPV cfs wacc = 
    let 
        n = List.length cfs
        df = calcDF wacc n
        cfs_df = zip cfs df
    in
        List.map (\x -> (Tuple.first x) * (Tuple.second x)) cfs_df
        |> List.foldl (+) 0 

Example:

calcNPV [100,100,100] 0.1

-- returns 273.553719

Thanks for your help!

As for how to change your code to accept Maybe values, you can change calcNPV to have a type signature of calcNPV : List (Maybe Float) -> Maybe Float -> Html msg and you’d call it like so calcNPV [ Just 100, Nothing, Just 100 ] (Just 0.1).

As far as idiomatic Elm goes, I’m very curious what things like NPV and wacc mean. Part of idiomatic Elm is to be explicit. Maybe these abbreviations mean something in a field of knowledge I’m not overly familiar with, such as mathematics or chemistry? Without that context, it’s a little.difficult for me to understand what the code is supposed to be doing. But it doesn’t look visually different in shape from most Elm I see.

Thanks for your swift response and for your help. Very much appreciated.

An sorry for being not precise enough.

Regarding type Maybe: my question was rather how to use type Maybe and still be able to calculate. Basically it should accomplish the same calculation but return an error if Nothing is in cfs or wacc. I am really struggling with Maybe type when doing calculations because I am not able to find a good way to check if the value is Just or Nothing. In other languages I could use a for loop and check each value.

E.g. how do I take the sum of a List of Maybe? I cannot do:

values = [Just 100, Just 200, Just 100]
List.foldl (+) 0 values

which is what my approach would be if I had a List of floats.

Regarding the abbreveations:npv mean net present value and wacc means weighted average of capital. It just a toy example I am trying to implement (see e.g. here).

Thanks for your help!

1 Like

To add two Maybe number values together, you need to first make sure they both are actually Just numbers.

If either of them are Nothing, you don’t want to perform the add operation.

The Maybe.map2 function does this check for you!

So instead of using (+) directly, you’ll need something like this:

values = [Just 100, Just 200, Just 100 ]

List.foldl
  (Maybe.map2 (+))
  (Just 0)
  values

Thanks for your help. I don’t fully understand why this works and I will have to chew on it a bit. But it is so much more concise than any of my attempts (using case of etc.).

It depends on how you want to treat the Nothing values.

if you just want to ignore them you go:

sum : List (Maybe Float) -> Float 
sum list = 
    List.filterMap identity list |> List.sum

If you want to add all values but return Nothing if there is any Nothing in the list:

sum : List (Maybe Float) -> Maybe Float 
sum list = 
    List.foldl (Maybe.map2 (+)) (Just 0) list 

Also, if you want to sacrifice a little bit of explicitness for conciseness you can also write those two point-free style. :wink:

sum : List (Maybe Float) -> Float 
sum = 
    List.filterMap identity >> List.sum

and

sum : List (Maybe Float) -> Maybe Float 
sum = 
    List.foldl (Maybe.map2 (+)) (Just 0) 

Later Edit. Since you just started playing with Elm: List.filterMap is designed to take a list element and return either Just mappedElement if you want to keep the element or Nothing if you want to filter it out. identity is a function that just returns what it is given. So, List.filterMap identity on a list of Maybe has the effect of filtering out the Nothing elements and converting the Just element to element.

Maybe.map2 (+) is a partial application of Maybe.map2 where the first argument is a function that adds the next two arguments. (+) is the prefix version of the infix function +. So, 1+2 is equivalent to (+) 1 2.

2 Likes

The “fully expanded” form of this, that might help you understand it better is:

List.foldl (\maybeElement maybeAccumulator -> Maybe.map2 (\element accumulator -> element + accumulator) maybeElement maybeAccumulator) (Just 0)

1 Like

Thanks for all your comments. This is very helpful for me to better understand the language. I will post my refactored code once I have implemented it. This is my refactored code if cfs and wacc are of type float

calcNPV : List Float -> Float -> Float
calcNPV cashflows wacc = 
    let
        time = List.length cashflows |> List.range 0 |> List.map toFloat 
        waccs = List.repeat (List.length cashflows)  wacc

        calcPV : Float -> Float -> Float -> Float
        calcPV cf i t = cf / (1+i)^t
    in
        List.map3 calcPV cashflows waccs time |> List.foldl (+) 0

Thanks. Easier to write and easier to read.

Here is my refactored code taking type Maybe as input.

calcMaybeNPV : List (Maybe Float) -> Maybe Float -> Maybe Float
calcMaybeNPV maybecashflows maybewacc = 
    let
        time = List.length maybecashflows |> List.range 0 |> List.map (\x -> Just (toFloat x))
        waccs = List.repeat (List.length maybecashflows)  maybewacc 

        calcPV : Maybe Float -> Maybe Float -> Maybe Float -> Maybe Float
        calcPV cf i t = 
            Maybe.map3 (\a b c -> a / (1+b)^c) cf i t
    in
        List.map3 calcPV maybecashflows waccs time |> List.foldl (Maybe.map2 (+)) (Just 0)

I learned a lot. Thanks for all your help!

Here’s an example of how you can write calcPV with case of. If you have 2 or 3 values, you can create temporary tuple from them to match them all at once:

calcPV : Maybe Float -> Maybe Float -> Maybe Float -> Maybe Float
calcPV cf i t =
    case (cf, i, t) of
        (Just cf_, Just i_, Just t_) ->
            Just <| cf_ / (1+i_)^t_

        _ ->
            Nothing

Here are some alternative implementations to your function:

calcMaybeNPV : List (Maybe Float) -> Maybe Float -> Maybe Float
calcMaybeNPV maybecashflows maybewacc =
    let
        calcPV time mcf =
            Maybe.map2 (\cf wacc -> cf / (1 + wacc) ^ toFloat time) mcf maybewacc
    in
    List.indexedMap calcPV maybecashflows
        |> List.foldl (Maybe.map2 (+)) (Just 0)


calcMaybeNPV2 : List (Maybe Float) -> Maybe Float -> Maybe Float
calcMaybeNPV2 maybecashflows maybewacc =
    let
        calcPV wacc cashflows =
            List.indexedMap (\time cf -> cf / (1 + wacc) ^ toFloat time) cashflows |> List.sum
    in
    List.foldr (Maybe.map2 (::)) (Just []) maybecashflows
        |> Maybe.map2 calcPV maybewacc

 

calcMaybeNPV3 : List (Maybe Float) -> Maybe Float -> Maybe Float
calcMaybeNPV3 maybecashflows maybewacc =
    case maybewacc of
        Nothing ->
            Nothing

        Just wacc ->
            let
                calcPV mcfs ( time, acc ) =
                    case mcfs of
                        [] ->
                            Just acc

                        Nothing :: _ ->
                            Nothing

                        (Just cf) :: xs ->
                            calcPV xs ( time + 1, acc + (cf / (1 + wacc) ^ toFloat time) )
            in
            calcPV maybecashflows ( 0, 0 )

They should do the same thing.

The third one has the advantage that it will exit as soon as it encounters any Nothing in the maybecashflows.

You can also extract the complex part that does the calculations in a function that deals only with valid wacc:


calcMaybeNPV : List (Maybe Float) -> Float -> Maybe Float
calcMaybeNPV maybecashflows wacc =
    let
        calcPV mcfs ( time, acc ) =
            case mcfs of
                [] ->
                    Just acc

                Nothing :: _ ->
                    Nothing

                (Just cf) :: xs ->
                    calcPV xs ( time + 1, acc + (cf / (1 + wacc) ^ toFloat time) )
    in
    calcPV maybecashflows ( 0, 0 )


-- Usage would be like: 
maybeNPV =  Maybe.andThen (calcMaybeNPV maybecashflows) maybewacc

Thanks for your suggestions. Your suggestions really broadened my understanding of how to approach thinks in a functional language and Elm, respectively.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.