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
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. 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.
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.
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
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
Thanks for your suggestions. Your suggestions really broadened my understanding of how to approach thinks in a functional language and Elm, respectively.